@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.
- package/HOW_TO_USE.md +46 -4
- package/README.md +7 -5
- package/browserstack.example.yaml +15 -3
- package/docs/user-guide.html +62 -10
- package/forgeqe.example.yaml +25 -18
- package/package.json +2 -2
- package/prompts/README.md +1 -0
- package/prompts/brain.md +7 -1
- package/prompts/defect-rca.md +43 -0
- package/prompts/test-agent.md +12 -2
- package/prompts/test-reviewer.md +5 -2
- package/prompts/test-rules.md +8 -4
- package/py/forgeqe_engine/browser.py +79 -12
- package/py/forgeqe_engine/service.py +12 -0
- package/skills/dotnet-nunit-playwright/SKILL.md +2 -1
- package/skills/playwright-typescript/SKILL.md +2 -1
- package/src/bi.ts +382 -302
- package/src/brain/index.ts +1 -1
- package/src/capabilities/guide.ts +3 -3
- package/src/capabilities/registry.ts +34 -6
- package/src/cases/bugs.ts +209 -22
- package/src/cases/defectHistory.ts +193 -0
- package/src/cases/defects.ts +255 -0
- package/src/cases/github.ts +162 -0
- package/src/cases/goal.ts +1 -1
- package/src/cases/index.ts +23 -4
- package/src/cases/predict.ts +372 -0
- package/src/cases/rca.ts +258 -0
- package/src/cases/readiness.ts +38 -1
- package/src/cases/repro.ts +202 -0
- package/src/cases/types.ts +8 -1
- package/src/cli/help.ts +16 -7
- package/src/cli/index.ts +56 -15
- package/src/code/graph/graph.ts +511 -0
- package/src/code/graph/locators.ts +398 -0
- package/src/code/graph/parse.ts +439 -0
- package/src/code/graph/tools.ts +113 -0
- package/src/code/handoff.ts +93 -18
- package/src/code/loop.ts +30 -5
- package/src/code/profile.ts +14 -3
- package/src/code/replay.ts +1 -1
- package/src/code/review.ts +22 -0
- package/src/config.ts +160 -56
- package/src/mcp/attachtools.ts +223 -0
- package/src/mcp/commands.ts +1 -1
- package/src/mcp/defecttools.ts +106 -0
- package/src/mcp/devicetools.ts +125 -0
- package/src/mcp/external.ts +42 -1
- package/src/mcp/tools.ts +92 -9
- package/src/monitor/run.ts +1 -1
- package/src/report/data.ts +14 -0
- package/src/report/html.ts +15 -3
- package/src/report/template.html +44 -43
- package/src/run.ts +9 -2
- package/src/suite/report.ts +44 -11
- package/src/suite/run.ts +163 -16
- package/src/tui/App.tsx +9 -3
- package/src/usage.ts +1 -1
- package/testmu.example.yaml +14 -4
- 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.
|
package/prompts/test-agent.md
CHANGED
|
@@ -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
|
|
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.
|
package/prompts/test-reviewer.md
CHANGED
|
@@ -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
|
|
package/prompts/test-rules.md
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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"] =
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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.
|