07.05 — Scénarios randomness (4 levels)#
Quatre niveaux de chaos progressifs. On commence simple, on monte en complexité, on stresse de plus en plus Ocarina.
Campagne#
# src/tests/campaigns/randomness.py
def create_igoristan_randomness_campaign(*, drivers_pool) -> TestCampaign:
return TestCampaign(
name="Randomness",
suites=[create_randomness_test_suite(drivers_pool=drivers_pool)],
)Suite#
# src/tests/suites/randomness.py
def create_randomness_test_suite(*, drivers_pool) -> TestSuite:
return TestSuite(
name="Randomness",
tests=[
test_random_error_page, # level 1
test_random_loaders_page, # level 1
test_dsed, # level 2
test_madness, # level 2
test_chaotic_form, # level 3
test_walkthrough, # level 4
],
drivers_pool=drivers_pool,
)Levels#
| Level | Mécanique exercée | Difficulté |
|---|---|---|
| 1 | DSL de base (drive_page, act) | facile |
| 2 | match_page / when | moyen |
| 3 | HumanizedDriver + Watcher | difficile |
| 4 | Walkthrough multi-pages chaotiques | difficile |
Level 1 — Random Error Page#
# tests/scenarios/randomness/level_1/random_error_page.py
def scenario_random_error_page(driver, logger):
page = RandomErrorPage(driver=driver)
return [
drive_page(
act(page, open_random_error_page)...,
act(page, verify_random_error_page)...,
),
]
test_random_error_page = create_selenium_test(
name="Random Error Page - smoke",
test_scenario=lambda driver, logger: Scenario(test_chain=scenario_random_error_page(driver, logger)),
)Test volontairement flaky, exerce le hook on_failure.
Level 1 — Random Loaders#
def scenario_random_loaders_page(driver, logger):
page = RandomLoadersPage(driver=driver)
return [
drive_page(
act(page, open_random_loaders_page)...,
act(page, verify_random_loaders_page)...,
),
]Vérifie juste que la page finit par s’afficher en contenant les informations attendues.
Level 2 — DSED (Donkey Sausage Eater Detector)#
# tests/scenarios/randomness/level_2/dsed.py
def scenario_dsed(driver, logger):
page_loading_or_result = DsedIdsBypassedPage(driver=driver)
check_that_page = DsedMatchers(driver=driver)
return [
drive_page(act(page_loading_or_result, open_dsed_page)...),
match_page(branches=[
when(check_that_page.is_approved,
name="approved",
then=[drive_page(act(page_loading_or_result, verify_approved)...)]),
when(check_that_page.is_disapproved,
name="disapproved",
then=[drive_page(act(page_loading_or_result, verify_disapproved)...)]),
]),
]- La page peut rendre
Approved(70%) ouDisapproved(30%). match_pagebranche selonis_approved/is_disapproved.- Chaque branche vérifie le rendu correspondant.
Notes :
- Les matchers (
is_approved,is_disapproved) sont des méthodes du POMDsedMatchers, distinctes desverify(cf. Holy Book : matcher ≠ verify). match_pageici est lematch_pageadapté côté projet (avecraised_exceptions=transient_errors).
Level 2 — Madness#
# tests/scenarios/randomness/level_2/madness.py
def scenario_madness(driver, logger):
on_madness = MadnessBasePage(driver=driver)
check_that_page = MadnessMatchers(driver=driver)
return [
drive_page(act(on_madness, open_madness_page)...),
match_page(branches=[
when(check_that_page.has_cors,
name="cors",
then=[drive_page(act(MadnessCorsPage(driver=driver), verify_cors_story)...)]),
when(check_that_page.has_this_is_bastia,
name="this_is_bastia",
then=[drive_page(act(MadnessThisIsBastiaPage(driver=driver), verify_this_is_bastia_story)...)]),
]),
]Pattern identique à DSED. Variante : on instancie des POMs spécifiques dans chaque branche (MadnessCorsPage, MadnessThisIsBastiaPage), pas un seul POM polymorphe.
Level 3 — Chaotic Form (avec HumanizedDriver + Watcher)#
# tests/scenarios/randomness/level_3/chaotic_form.py
def _send_chaotic_form(driver, logger):
on_chaotic_form = ChaoticFormPage(driver=driver)
...
return [
drive_page(
act(on_chaotic_form, open_chaotic_form_page)...,
act(on_chaotic_form, fill_form_with_humanized_typing)...,
act(on_chaotic_form, submit_form)...,
act(on_chaotic_form, verify_submission_success)...,
),
]
test_send_chaotic_form = create_selenium_test(
name="Send the chaotic form",
test_scenario=lambda driver, logger: Scenario(
test_chain=_send_chaotic_form(
HumanizedDriver(
driver,
wpm=125,
typo_rate=0.14,
hesitation_rate=0.02,
burst_rate=0.35,
late_correction_rate=0.6,
),
logger,
),
watchers=[
create_selenium_watcher(
callback=catch_me_if_you_can_cb,
name="catch-me-if-you-can",
poll_interval=0.8,
),
],
),
)HumanizedDriverwrappe ledriverSelenium pour simuler une saisie humaine (cf.07-humanized-driver.md).wpm=125(mots/minute),typo_rate=0.14(14% de typos), etc.watchers=[...]: un watcher tourne en daemon pendant le test.catch_me_if_you_can_cb: détecte les éléments.catch-me-if-you-canparasites (cf.09-watcher-catch-me.md).poll_interval=0.8: poll toutes les 800 ms.HumanizedDriverest passé au scénario au lieu du driver : intercepte tous lesfind_elementdu POM.
Level 4 — Walkthrough#
# tests/scenarios/randomness/level_4/walkthrough.py
def scenario_walkthrough(driver, logger):
on_homepage = Homepage(driver=driver)
return [
drive_page(act(on_homepage, open_homepage)..., act(on_homepage, verify_homepage)...),
# ... ouverture séquentielle de chaque page de l'Igoristan ...
# ... vérification de leur titre ...
# ... retour homepage ...
]Un test qui fait un « tour complet », une promenade tranquille jusque dans les limbes de l’Igoristan.
HumanizedDriver instancié depuis l’extérieur#
test_scenario=lambda driver, logger: Scenario(
test_chain=_send_chaotic_form(HumanizedDriver(driver, ...), logger),
...
)- Le vrai
driverarrive depuis la pool. - On le wrappe dans
HumanizedDriver(driver, ...). - On passe le wrapper au scénario.
- Tous les POMs reçoivent le wrapper.
Ici : pom._driver = humanized_driver.
Quand le POM fait self._driver.find_element(...).send_keys(...), c’est le humanized qui intercepte et fait la saisie lente avec typos.