@capagents/forgeqe 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/HOW_TO_USE.md +46 -4
  2. package/README.md +7 -5
  3. package/browserstack.example.yaml +15 -3
  4. package/docs/user-guide.html +62 -10
  5. package/forgeqe.example.yaml +25 -18
  6. package/package.json +2 -2
  7. package/prompts/README.md +1 -0
  8. package/prompts/brain.md +7 -1
  9. package/prompts/defect-rca.md +43 -0
  10. package/prompts/test-agent.md +12 -2
  11. package/prompts/test-reviewer.md +5 -2
  12. package/prompts/test-rules.md +8 -4
  13. package/py/forgeqe_engine/browser.py +79 -12
  14. package/py/forgeqe_engine/service.py +12 -0
  15. package/skills/dotnet-nunit-playwright/SKILL.md +2 -1
  16. package/skills/playwright-typescript/SKILL.md +2 -1
  17. package/src/bi.ts +382 -302
  18. package/src/brain/index.ts +1 -1
  19. package/src/capabilities/guide.ts +3 -3
  20. package/src/capabilities/registry.ts +34 -6
  21. package/src/cases/bugs.ts +209 -22
  22. package/src/cases/defectHistory.ts +193 -0
  23. package/src/cases/defects.ts +255 -0
  24. package/src/cases/github.ts +162 -0
  25. package/src/cases/goal.ts +1 -1
  26. package/src/cases/index.ts +23 -4
  27. package/src/cases/predict.ts +372 -0
  28. package/src/cases/rca.ts +258 -0
  29. package/src/cases/readiness.ts +38 -1
  30. package/src/cases/repro.ts +202 -0
  31. package/src/cases/types.ts +8 -1
  32. package/src/cli/help.ts +16 -7
  33. package/src/cli/index.ts +56 -15
  34. package/src/code/graph/graph.ts +511 -0
  35. package/src/code/graph/locators.ts +398 -0
  36. package/src/code/graph/parse.ts +439 -0
  37. package/src/code/graph/tools.ts +113 -0
  38. package/src/code/handoff.ts +93 -18
  39. package/src/code/loop.ts +30 -5
  40. package/src/code/profile.ts +14 -3
  41. package/src/code/replay.ts +1 -1
  42. package/src/code/review.ts +22 -0
  43. package/src/config.ts +160 -56
  44. package/src/mcp/attachtools.ts +223 -0
  45. package/src/mcp/commands.ts +1 -1
  46. package/src/mcp/defecttools.ts +106 -0
  47. package/src/mcp/devicetools.ts +125 -0
  48. package/src/mcp/external.ts +42 -1
  49. package/src/mcp/tools.ts +92 -9
  50. package/src/monitor/run.ts +1 -1
  51. package/src/report/data.ts +14 -0
  52. package/src/report/html.ts +15 -3
  53. package/src/report/template.html +44 -43
  54. package/src/run.ts +9 -2
  55. package/src/suite/report.ts +44 -11
  56. package/src/suite/run.ts +163 -16
  57. package/src/tui/App.tsx +9 -3
  58. package/src/usage.ts +1 -1
  59. package/testmu.example.yaml +14 -4
  60. package/src/cache.ts +0 -92
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: defect-rca
3
+ description: "Writes a root-cause hypothesis for a defect from the evidence of its runs and the code of the product."
4
+ used_by: "forgeqe suite (defects.analyze)"
5
+ kind: agent
6
+ ---
7
+ {{> conventions}}
8
+
9
+ ## Role
10
+ You analyse the cause of software defects. You can read files. You can run git log, git show, git diff, git blame, git grep, git ls-files, and ls. You cannot change files, run the application, or run tests.
11
+
12
+ ## Objective
13
+ Find the most likely cause of one defect that ForgeQE found in a test run. Give a hypothesis that a developer can check. ForgeQE shows it in the report and adds it to the bug as a hypothesis, not as a verdict.
14
+
15
+ ## Execution contract
16
+ **Preconditions**: the defect, the evidence from its runs, and the steps to reproduce it. Also the failing request and the suspect commits, when ForgeQE found them.
17
+ **Tools**: read files and the git commands above, in the repository of the product.
18
+ **Procedure**:
19
+ 1. Read the evidence. Find the exact point of failure: a request and its status, an error message, or a wrong result.
20
+ 2. Find the code that serves that point. Use git grep for the route, the error message, and the names on the page.
21
+ 3. Read that code, and the code that it calls, as far as the cause.
22
+ 4. Read each suspect commit with git show. Decide if its change can cause the failure.
23
+ 5. Write the hypothesis, the evidence for it, and the checks that confirm or reject it.
24
+ **Stop when**: one hypothesis explains all the evidence. Or stop when you have read the failing code and the suspect commits.
25
+ **On failure**: when you cannot find the code, give a hypothesis from the evidence only. Set the confidence to low, and say what you searched.
26
+ **Evidence**: each fact with its source, for example "network.json: GET /api/cart/:id answered 500" or "src/cart/service.ts:42 reads price before the cart loads".
27
+
28
+ ## Rules
29
+ - The hypothesis MUST explain every item of evidence. When it does not explain an item, you MUST say which item.
30
+ - You MUST give a fact only from the evidence or from a file that you read. You MUST NOT invent a file, a line, a commit, or an identifier.
31
+ - A suspect commit is only a suspect. You MUST NOT call it the cause unless its change explains the failure.
32
+ - Confidence high MUST mean that you read the code that fails and the change that broke it. Confidence medium MUST mean that you read the code that fails. Confidence low means the evidence only.
33
+ - When the code that serves the point of failure is not in the repository, the confidence MUST be low.
34
+ - When the cause can be the environment or the test data, you MUST say so. Give the check that tells them apart.
35
+ - Each check to confirm MUST be an action for a developer: a request to send, a log to read, or a test to run.
36
+ - You SHOULD keep the hypothesis to two sentences.
37
+ - You MUST put each file path, code name, command, and URL in backticks, for example `server/cart.ts:42` or `getCart`.
38
+ - You MUST put text that the page shows in double quotes, not in backticks.
39
+ - You MUST NOT use other Markdown: no headings, lists, bold, or links. The report shows your words as text.
40
+ - You MUST NOT propose a fix that you did not check against the code.
41
+
42
+ ## Output
43
+ The structured result: hypothesis, confidence, layer, component, evidence, confirm, and files.
@@ -18,9 +18,18 @@ Move a verified browser flow into a test that fits this repository, so that it p
18
18
 
19
19
  ## Execution contract
20
20
  **Preconditions**: you know the test framework, its configuration, how to run one test, and the page objects, fixtures, helpers, test data, and browser configuration that this test uses.
21
- **Tools**: your own tools to read and edit files, and to build. After a repeated failure, ForgeQE turns on the live_browser_* tools: a Chrome with the same setup as the browser of ForgeQE.
21
+ **Tools**: your own tools to read and edit files, and to build. The code_* tools: the code graph of this repository, which ForgeQE keeps up to date as you edit. After a repeated failure, ForgeQE turns on the live_browser_* tools: a Chrome with the same setup as the browser of ForgeQE.
22
22
  **Procedure**:
23
23
  1. Learn the repository. With notes from earlier jobs, start from them, and read only the files that this test uses. Without notes, read AGENTS.md, README, and the contributing notes.
24
+ With the code_* tools, find code with them before grep or reading files:
25
+ - code_find: page objects, methods, and tests by name or words, or by the text a locator uses.
26
+ - code_outline: the signatures and locators of a file or class, or the source of one member (Class.member).
27
+ You MUST NOT read a whole file to learn what it holds when the handoff's outlines or code_outline give it. Read a whole file only to edit it, and read a member's body with code_outline Class.member.
28
+ When the handoff gives "A test in this repository to follow", you MUST take the conventions of tests from it, and MUST NOT outline or read other tests to learn them.
29
+ The repository section of your first message lists the browser settings files and their values. You MUST read a settings file only to change it.
30
+ - code_element: the page-object member that already locates an element of the handoff, whichever locator it uses.
31
+ - code_references: what calls a member, before you change it.
32
+ - code_impact: the tests that a change to shared files reaches.
24
33
  2. Write the test from the path of the handoff: its steps in sequence, with their locators and waits.
25
34
  3. Reply with the command. ForgeQE runs it, and sends you the failure message when it fails. The full output is in a file.
26
35
  4. To correct a failure, first use what ForgeQE sends: the failing element on both pages, and the saved snapshot.
@@ -36,7 +45,8 @@ Locators:
36
45
  - You MUST NOT open the page or search page memory to find a ✓ element again.
37
46
  - You MUST take each locator from the handoff, the replay script, the replay snapshots, or a live_browser_* snapshot.
38
47
  - You MUST NOT guess a locator. A wrong guess costs a full build and test run.
39
- - When the repository has a locator for the same element, you MUST keep the locator of the repository.
48
+ - When the repository has a locator for the same element, you MUST reuse it where it is defined. A step's "in this repository" line names it. For a step without that line, ask code_element before you write a new locator.
49
+ - When that locator is XPath, positional, or a CSS path, and the handoff has a ✓ semantic locator (role, label, text, or test id) for the same element, you MUST replace it with the ✓ locator where it is defined, and say so in notes.
40
50
 
41
51
  Steps and checks:
42
52
  - When the handoff says that a step failed in the replay, you MUST correct it from the error and the page snapshot that the handoff reports.
@@ -24,22 +24,25 @@ A test is approved only when all these conditions are true:
24
24
 
25
25
  ## Problem types
26
26
  - correctness: the test does not do what the goal says, from start to end, or not reliably. Examples are a missing step or check, a weaker assertion, live values in assertions, and ForgeQE files that the test reads.
27
- - standards: the test passes and does what the goal says, but not in the way of the repository. Examples are test data in a page object, a method that is not generic, a hard wait, and scratch files.
27
+ - standards: the test passes and does what the goal says, but not in the way of the repository. Examples are test data in a page object, a method that is not generic, a hard wait, an XPath or positional locator where a semantic one finds the element, a deprecated API of the installed framework version, and scratch files.
28
28
 
29
29
  ## Inputs
30
30
  - The message from ForgeQE: the goal, the checks that the browser agent made, and the files that this job changed, with their contents.
31
+ - When the code graph is on: outlines of the classes that the changed code uses, the browser settings of the repository, and the code_* tools (code_outline Class.member gives one member's source).
31
32
  - ForgeQE gets those files from a comparison of the working tree with its state before the job.
32
33
  - The handoff, and other files of the repository, when a point depends on them.
33
34
 
34
35
  ## Rules
35
36
  Scope:
36
37
  - You MUST review from the changed files. You SHOULD read the handoff or other files only when a point depends on them.
38
+ - You MUST NOT read a file to check a signature, a locator, or a browser setting that the outlines or the settings in the message give. To check a member's body, use code_outline Class.member.
37
39
  - You MUST NOT report files that the user changed or added before the job.
38
40
  - You MUST NOT report the files of ForgeQE: .forgeqe/, forgeqe.yaml, and llms.json.
39
41
  - In a file that the user changed before the job, you MUST review only the edits of this job.
40
42
 
41
43
  Locators and values:
42
44
  - You MUST NOT ask to replace locators and texts that the handoff took from its replay script and checks. They matched the live site.
45
+ - When the message lists what the code graph of ForgeQE found, you MUST check each point in the changed files. A new member that locates an element an existing member already locates is a standards problem: the correction is to reuse the existing member. A brittle locator is a standards problem when the handoff gives a ✓ semantic locator for the same element.
43
46
  - You MUST accept a value that the test case names, such as a user name. Credentials MUST still come from the environment.
44
47
 
45
48
  Known defects (the handoff has a "Known defect" section):
@@ -52,7 +55,7 @@ Problems:
52
55
  - You MUST give all the problems in your first review. The coding agent then corrects them in one round.
53
56
  - For each problem, you MUST give the file, what is wrong, the correction, and its type: correctness or standards.
54
57
  - You MUST NOT ask for checks, data handling, or behaviour that the test case and the handoff do not ask for.
55
- - You MUST NOT report style points that the linters and conventions of the repository accept.
58
+ - You MUST NOT report style points that the linters and conventions of the repository accept. Locators, waits, assertions, and deprecated APIs are not style points: judge them by the test rules, also when older tests of the repository do otherwise.
56
59
  - You MUST set approved to true when there is no correctness problem, also when there are standards problems. ForgeQE then asks for those corrections in one round.
57
60
  - When there is nothing to correct, you MUST approve.
58
61
 
@@ -33,7 +33,11 @@ When the page of the test differs from what ForgeQE saw:
33
33
  - With blocked, you MUST give what you compared and what you saw, with the path of a screenshot or a trace.
34
34
 
35
35
  ## Code standards
36
- - You MUST follow the conventions of the repository: .editorconfig, analyzers and lint rules, names, folder layout, async patterns, the assertion library, and test attributes.
36
+ - You MUST follow the conventions of the repository for its structure: .editorconfig, analyzers and lint rules, names, folder layout, base classes, fixtures, the assertion library, and test attributes.
37
+ - You MUST write the code itself with the current best practice of the installed framework version, also when older tests of the repository do not. The test stack in your prompt gives the versions.
38
+ - Locators, in this order of preference: role with accessible name, label, placeholder, text, test id, then CSS. You MUST NOT use XPath, or positions (nth, :nth-child, index) to pick an element, when one of the others finds it. In a framework without role locators, use its nearest semantic one (Testing Library queries, aria/ selectors) or a stable attribute.
39
+ - You MUST assert with the retrying assertions of the framework, and wait for outcomes (an assertion, a URL, a response), never for time. You MUST NOT use sleeps, fixed timeouts, or deprecated APIs of the installed version.
40
+ - You MUST NOT rewrite existing tests to modern style. When the code you reuse is in an older style, say so in notes.
37
41
  - The build MUST NOT get more warnings.
38
42
  - Page objects, components, and helpers MUST be generic. Each method takes the items that it acts on as parameters, and gives back what the test checks.
39
43
  - You MUST NOT put test data, expected values, or URLs with query strings in page objects. You MUST make locators from the parameters.
@@ -49,11 +53,11 @@ When the page of the test differs from what ForgeQE saw:
49
53
  ## Overlays and page versions
50
54
  - The notes of the browser agent tell when a cookie banner, sign-in prompt, promotion, or other overlay came into view, and what the agent did.
51
55
  - An overlay can come into view at a different time, or not at all. You MUST handle it where it comes into view.
52
- - When the handoff lists overlays that the run closed, you MUST write one helper that closes each visible overlay, in the sequence of the handoff, top one first.
56
+ - When the handoff lists one overlay that the run closed, and the framework has an overlay handler (Playwright 1.42 and later: page.addLocatorHandler, Page.AddLocatorHandlerAsync), you MUST register the handler once, in the fixture or the base class setup, with the close control of the handoff.
57
+ - When the handoff lists two or more overlays, or the framework has no overlay handler, you MUST write one helper that closes each visible overlay, in the sequence of the handoff, top one first.
53
58
  - For each overlay, the helper MUST check its close control with a plain visibility check, click it, and wait until the overlay is hidden.
54
59
  - You MUST call the helper after each navigation, and immediately before each step that opens a thing, such as a date picker or a menu.
55
- - You MUST NOT use the locator handlers of the framework (page.addLocatorHandler, Page.AddLocatorHandlerAsync) or a combined locator (locator.or) for overlays.
56
- - Two overlays at the same time block each other, and strict mode then fails.
60
+ - You MUST NOT use overlay handlers or a combined locator (locator.or) for two or more overlays. They can show at the same time: the click of one handler is blocked by the other overlay, handlers do not run inside a handler, and strict mode fails on a combined locator.
57
61
  - You MUST do the flow again, not the tries of the agent.
58
62
  - A site can give different versions of a page to different visits (A/B tests). When you see a different version, you MUST write steps that work on each version.
59
63
  - You SHOULD find elements by what the versions have in common (role and accessible name). You MUST say in notes which versions you saw.
@@ -3,11 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import contextlib
6
7
  import json
7
8
  import logging
8
9
  import os
9
10
  import time
10
- from importlib.metadata import version as package_version
11
11
  from pathlib import Path
12
12
  from typing import Any, Dict, Optional
13
13
  from urllib.parse import quote, urlparse
@@ -140,13 +140,6 @@ def _remote_secret(name: str, provider: str, label: str) -> str:
140
140
  raise RuntimeError(f'{provider}: set {name} ({label}) in the environment, or save it with "forgeqe auth login {provider}".')
141
141
 
142
142
 
143
- def _playwright_version() -> Optional[str]:
144
- try:
145
- return package_version("playwright")
146
- except Exception:
147
- return None
148
-
149
-
150
143
  def _browserstack_caps(remote: Dict[str, Any]) -> Dict[str, Any]:
151
144
  caps: Dict[str, Any] = {
152
145
  "browser": str(remote.get("browser") or ""),
@@ -163,10 +156,7 @@ def _browserstack_caps(remote: Dict[str, Any]) -> Dict[str, Any]:
163
156
  if remote.get("name"):
164
157
  caps["name"] = str(remote["name"])
165
158
  if remote.get("local") is True:
166
- caps["browserstack.local"] = True
167
- pw = _playwright_version()
168
- if pw:
169
- caps["client.playwrightVersion"] = pw
159
+ caps["browserstack.local"] = "true"
170
160
  extra = remote.get("extra")
171
161
  if isinstance(extra, dict):
172
162
  caps.update(extra)
@@ -229,6 +219,83 @@ def browser_connection_label(provider: str, launch: Dict[str, Any], remote: Opti
229
219
  return " · ".join(x for x in [provider, browser, target] if x)
230
220
 
231
221
 
222
+ _PAGE_PASSWORD_JS = """(() => ({
223
+ url: location.href,
224
+ filled: Array.from(document.querySelectorAll('input[type=password]')).some((e) => (e.value || '').length > 0),
225
+ }))()"""
226
+
227
+
228
+ def typed_into_password(history_item: Any) -> bool:
229
+ """Whether a step typed into a password field: its actions line up with the elements they touched."""
230
+ actions = getattr(getattr(history_item, "model_output", None), "action", None) or []
231
+ elements = getattr(getattr(history_item, "state", None), "interacted_element", None) or []
232
+ for action, element in zip(actions, elements):
233
+ attributes = (getattr(element, "attributes", None) or {}) if element is not None else {}
234
+ if str(attributes.get("type") or "").lower() != "password":
235
+ continue
236
+ try:
237
+ name = next(iter(action.model_dump(exclude_unset=True)), "")
238
+ except Exception:
239
+ name = ""
240
+ if any(word in name for word in ("input", "type", "fill")):
241
+ return True
242
+ return False
243
+
244
+
245
+ class FreshTabAfterPassword:
246
+ """
247
+ Remote grids only: after a password form is submitted, TestMu AI's Chrome stops taking real mouse and keyboard
248
+ input on that tab (most likely its password manager's bubble), while the page itself still works: clicks report
249
+ success and change nothing. The same URL in a fresh tab takes input again, with the session's cookies intact.
250
+ Armed when a step types into a password field (or one is filled when a step ends); fires once the page has left
251
+ that URL with no filled password field, so a failed sign-in that stays on the form does not move tabs.
252
+ """
253
+
254
+ def __init__(self) -> None:
255
+ self.armed_at: Optional[str] = None
256
+
257
+ async def after_step(self, browser: Any, history_item: Any) -> Optional[str]:
258
+ """Returns the URL reopened in a fresh tab, or None."""
259
+ if typed_into_password(history_item):
260
+ self.armed_at = str(getattr(getattr(history_item, "state", None), "url", "") or "")
261
+ try:
262
+ session = await asyncio.wait_for(browser.get_or_create_cdp_session(), timeout=2)
263
+ r = await asyncio.wait_for(
264
+ session.cdp_client.send.Runtime.evaluate(
265
+ params={"expression": _PAGE_PASSWORD_JS, "returnByValue": True}, session_id=session.session_id
266
+ ),
267
+ timeout=2,
268
+ )
269
+ state = ((r or {}).get("result") or {}).get("value") or {}
270
+ except Exception:
271
+ return None
272
+ url = str(state.get("url") or "")
273
+ if self.armed_at is None:
274
+ if state.get("filled"):
275
+ self.armed_at = url
276
+ return None
277
+ if not url.startswith("http") or url == self.armed_at or state.get("filled"):
278
+ return None
279
+ self.armed_at = None
280
+ await reopen_in_fresh_tab(browser, url)
281
+ return url
282
+
283
+
284
+ async def reopen_in_fresh_tab(browser: Any, url: str) -> None:
285
+ """Open url in a new tab, which the agent then works in, and close the tab it was on."""
286
+ from browser_use.browser.events import CloseTabEvent, NavigateToUrlEvent
287
+
288
+ old = getattr(browser, "agent_focus_target_id", None)
289
+ event = browser.event_bus.dispatch(NavigateToUrlEvent(url=url, new_tab=True))
290
+ await event
291
+ await event.event_result(raise_if_any=True, raise_if_none=False)
292
+ if old and old != getattr(browser, "agent_focus_target_id", None):
293
+ with contextlib.suppress(Exception):
294
+ close = browser.event_bus.dispatch(CloseTabEvent(target_id=old))
295
+ await close
296
+ await close.event_result(raise_if_any=False, raise_if_none=False)
297
+
298
+
232
299
  async def _shutdown_browser_instance(browser: Any) -> None:
233
300
  """Stop the library gracefully, falling back to kill()."""
234
301
  if browser is None:
@@ -44,6 +44,7 @@ from .llm import (
44
44
  _agent_usage,
45
45
  )
46
46
  from .browser import (
47
+ FreshTabAfterPassword,
47
48
  browser_connection_label,
48
49
  browser_launch_kwargs,
49
50
  local_browser_launch_kwargs,
@@ -1105,6 +1106,14 @@ async def run_browser_task(req: RunRequest, emit: Emit, cancel_event: asyncio.Ev
1105
1106
  last = agent_ref.history.history[-1]
1106
1107
  except Exception:
1107
1108
  return
1109
+ if fresh_tab is not None:
1110
+ try:
1111
+ reopened = await fresh_tab.after_step(browser, last)
1112
+ except Exception as ex:
1113
+ reopened = None
1114
+ logger.warning("could not reopen the page in a fresh tab after sign-in: %s", ex)
1115
+ if reopened:
1116
+ emit({"type": "log", "level": "info", "text": f"Signed in on {browser_provider}: reopened {reopened} in a fresh tab, so the page takes input again"})
1108
1117
  results = getattr(last, "result", None) or []
1109
1118
  if mem is not None:
1110
1119
  mem.llm_steps = len(agent_steps(agent_ref.history.history))
@@ -1153,6 +1162,9 @@ async def run_browser_task(req: RunRequest, emit: Emit, cancel_event: asyncio.Ev
1153
1162
  if overlay is not None:
1154
1163
  await overlay.thinking()
1155
1164
 
1165
+ # Remote grids stop taking input on a tab after a password form is submitted: see FreshTabAfterPassword.
1166
+ fresh_tab = FreshTabAfterPassword() if browser_provider != "local" else None
1167
+
1156
1168
  async def _on_step_start(agent_ref: Any) -> None:
1157
1169
  if not collected_steps:
1158
1170
  await diag.start(browser)
@@ -26,7 +26,8 @@ agents: [scribe]
26
26
  - Locators: `Page.GetByRole(AriaRole.Button, new() { Name = "Save" })`, `GetByLabel`, `GetByPlaceholder`, `GetByText`, and `GetByTestId`, then CSS. Avoid XPath and position-based selectors.
27
27
  - Assert page state with retrying assertions: `await Expect(locator).ToBeVisibleAsync()`, `ToHaveTextAsync`, `ToHaveValueAsync`, and `await Expect(Page).ToHaveURLAsync(...)`. Use NUnit's `Assert.That` for plain values only, never on `await locator.IsVisibleAsync()`.
28
28
  - Wait for outcomes with `Page.WaitForURLAsync`, `Page.RunAndWaitForResponseAsync`, or an assertion.
29
- - Overlays that appear at random (cookie banners, surveys): register `Page.AddLocatorHandlerAsync(...)` in `[SetUp]` of the base class rather than clicking them inside a test.
29
+ - One overlay that appears at random (a cookie banner, a survey): register `Page.AddLocatorHandlerAsync(...)` in `[SetUp]` of the base class (Playwright 1.42 and later) rather than clicking it inside a test.
30
+ - Two or more that can show at once: write one helper that closes each visible one, top first, as the test rules describe. A handler's click is blocked while another overlay covers it.
30
31
 
31
32
  ## Data, secrets, attributes
32
33
  - Data: `[TestCase(...)]` or `[TestCaseSource(nameof(Cases))]`, or the repository's data files and builders. Compute dates when the test runs.
@@ -27,7 +27,8 @@ agents: [scribe]
27
27
  - Wait for outcomes, never time: no `page.waitForTimeout`. Use an assertion, `page.waitForURL`, or a `page.waitForResponse(...)` promise created before the click that triggers the request.
28
28
 
29
29
  ## Overlays that come and go
30
- - Cookie banners, chat prompts, and surveys that appear at random: register `page.addLocatorHandler(page.getByRole('button', { name: 'Accept all' }), async (button) => { await button.click() })` in a fixture or `beforeEach`, instead of clicking them inside the test.
30
+ - One cookie banner, chat prompt, or survey that appears at random: register `page.addLocatorHandler(page.getByRole('button', { name: 'Accept all' }), async (button) => { await button.click() })` in a fixture or `beforeEach` (Playwright 1.42 and later), instead of clicking it inside the test.
31
+ - Two or more that can show at once: write one helper that closes each visible one, top first, as the test rules describe. A handler's click is blocked while another overlay covers it.
31
32
 
32
33
  ## Data and secrets
33
34
  - Read secrets with `process.env.NAME`, and fail with a clear message when one is missing. If the config loads `dotenv`, add new names to `.env.example` without values.