@q-agent/agent 0.1.0 → 0.1.6
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/README.md +52 -1
- package/dist/src/api.js +54 -0
- package/dist/src/bus.js +27 -0
- package/dist/src/cli.js +20 -6
- package/dist/src/config.js +25 -0
- package/dist/src/ensureBrowser.js +8 -20
- package/dist/src/paths.js +152 -0
- package/dist/src/playwrightConfig.js +87 -42
- package/dist/src/report.js +4 -0
- package/dist/src/runner.js +342 -100
- package/dist/src/ui.js +629 -0
- package/dist/test/api.test.js +19 -0
- package/dist/test/playwrightConfig.test.js +84 -0
- package/dist/test/report.test.js +4 -0
- package/package.json +40 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Port of `api/app/services/playwright_runner.py`'s config template
|
|
4
|
-
* (`_PLAYWRIGHT_CONFIG_TEMPLATE` + `_write_config`) and the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* (`_PLAYWRIGHT_CONFIG_TEMPLATE` + `_write_config`) and the injected-fixtures
|
|
5
|
+
* shim (`_fixtures_ts` + `_apply_fixtures`), reproduced faithfully so a spec
|
|
6
|
+
* that runs on the server and one that runs via the Local Agent behave
|
|
7
|
+
* identically — including always-on DOM capture (raw + distilled).
|
|
8
8
|
*/
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,8 +41,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
})();
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.writeConfig = writeConfig;
|
|
44
|
-
exports.
|
|
45
|
-
exports.
|
|
44
|
+
exports.fixturesTs = fixturesTs;
|
|
45
|
+
exports.applyFixtures = applyFixtures;
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const PLAYWRIGHT_CONFIG_TEMPLATE = `import { defineConfig } from '@playwright/test';
|
|
@@ -83,14 +83,37 @@ function writeConfig(specDir, workers, headless, baseUrl = "", storageState = ""
|
|
|
83
83
|
fs.writeFileSync(path.join(specDir, "playwright.config.ts"), content, "utf-8");
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* TypeScript for a generated `fixtures.ts` that
|
|
87
|
-
* port of
|
|
86
|
+
* TypeScript for a generated `fixtures.ts` that captures the page DOM after
|
|
87
|
+
* every test, and optionally replays a captured sessionStorage — port of
|
|
88
|
+
* `_fixtures_ts`.
|
|
89
|
+
*
|
|
90
|
+
* The module re-exports Playwright's `test` extended with:
|
|
91
|
+
* - an `{auto: true}` fixture that, after each test, best-effort attaches the
|
|
92
|
+
* live page's raw HTML (`qagent-dom-raw`) and a distilled inventory of the
|
|
93
|
+
* page's interactable elements (`qagent-dom-distilled`) via `testInfo.attach`,
|
|
94
|
+
* so self-heal can ground on the real DOM. Wrapped in try/catch so it never
|
|
95
|
+
* fails a test.
|
|
96
|
+
* - (only when `replaySession`) a `context` override that restores the captured
|
|
97
|
+
* `sessionStorage` (MSAL/SPA tokens) for the current origin before app code runs.
|
|
88
98
|
*
|
|
89
99
|
* @param sessionFile Absolute path to the captured `sessionStorage.json`;
|
|
90
|
-
* embedded as a JSON-encoded string literal
|
|
91
|
-
*
|
|
100
|
+
* embedded as a JSON-encoded string literal, read at runtime only when
|
|
101
|
+
* `replaySession` is set.
|
|
102
|
+
* @param replaySession Whether to inject the sessionStorage-replay `context`
|
|
103
|
+
* override (DOM capture is always injected regardless).
|
|
92
104
|
*/
|
|
93
|
-
function
|
|
105
|
+
function fixturesTs(sessionFile, replaySession) {
|
|
106
|
+
const contextFixture = replaySession
|
|
107
|
+
? " context: async ({ context }, use) => {\n" +
|
|
108
|
+
" await context.addInitScript((sessions: Record<string, Record<string, string>>) => {\n" +
|
|
109
|
+
" try {\n" +
|
|
110
|
+
" const entries = sessions[location.origin];\n" +
|
|
111
|
+
" if (entries) for (const k in entries) window.sessionStorage.setItem(k, entries[k]);\n" +
|
|
112
|
+
" } catch {}\n" +
|
|
113
|
+
" }, SESSIONS);\n" +
|
|
114
|
+
" await use(context);\n" +
|
|
115
|
+
" },\n"
|
|
116
|
+
: "";
|
|
94
117
|
return ("import { test as base, expect } from '@playwright/test';\n" +
|
|
95
118
|
"import * as fs from 'fs';\n" +
|
|
96
119
|
"\n" +
|
|
@@ -98,47 +121,71 @@ function authFixturesTs(sessionFile) {
|
|
|
98
121
|
"let SESSIONS: Record<string, Record<string, string>> = {};\n" +
|
|
99
122
|
"try { SESSIONS = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8')); } catch {}\n" +
|
|
100
123
|
"\n" +
|
|
101
|
-
"export const test = base.extend({\n" +
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
124
|
+
"export const test = base.extend<{ _domCapture: void }>({\n" +
|
|
125
|
+
contextFixture +
|
|
126
|
+
" // Always-on DOM capture: after each test, snapshot the live page so the\n" +
|
|
127
|
+
" // runner (evidence) and self-heal loop (real selectors) can use it.\n" +
|
|
128
|
+
" _domCapture: [async ({ page }, use, testInfo) => {\n" +
|
|
129
|
+
" await use();\n" +
|
|
130
|
+
" try {\n" +
|
|
131
|
+
" const raw = await page.content();\n" +
|
|
132
|
+
" const rawPath = testInfo.outputPath('qagent-dom-raw.html');\n" +
|
|
133
|
+
" fs.writeFileSync(rawPath, raw, 'utf-8');\n" +
|
|
134
|
+
" await testInfo.attach('qagent-dom-raw', { path: rawPath, contentType: 'text/html' });\n" +
|
|
135
|
+
" } catch {}\n" +
|
|
136
|
+
" try {\n" +
|
|
137
|
+
" const snapshot = await page.evaluate(() => {\n" +
|
|
138
|
+
" const SEL = 'a,button,input,select,textarea,[role],[data-testid],[data-test],[id]';\n" +
|
|
139
|
+
" const elements = Array.from(document.querySelectorAll(SEL)).slice(0, 400).map((node) => {\n" +
|
|
140
|
+
" const el = node as HTMLElement;\n" +
|
|
141
|
+
" const text = (el.innerText || '').trim().slice(0, 80);\n" +
|
|
142
|
+
" return {\n" +
|
|
143
|
+
" tag: el.tagName.toLowerCase(),\n" +
|
|
144
|
+
" role: el.getAttribute('role') || undefined,\n" +
|
|
145
|
+
" testId: el.getAttribute('data-testid') || el.getAttribute('data-test') || undefined,\n" +
|
|
146
|
+
" id: el.id || undefined,\n" +
|
|
147
|
+
" name: el.getAttribute('name') || undefined,\n" +
|
|
148
|
+
" text: text || undefined,\n" +
|
|
149
|
+
" placeholder: el.getAttribute('placeholder') || undefined,\n" +
|
|
150
|
+
" type: el.getAttribute('type') || undefined,\n" +
|
|
151
|
+
" };\n" +
|
|
152
|
+
" });\n" +
|
|
153
|
+
" return { path: location.pathname, url: location.href, elements };\n" +
|
|
154
|
+
" });\n" +
|
|
155
|
+
" const distilledPath = testInfo.outputPath('qagent-dom-distilled.json');\n" +
|
|
156
|
+
" fs.writeFileSync(distilledPath, JSON.stringify(snapshot), 'utf-8');\n" +
|
|
157
|
+
" await testInfo.attach('qagent-dom-distilled', { path: distilledPath, contentType: 'application/json' });\n" +
|
|
158
|
+
" } catch {}\n" +
|
|
159
|
+
" }, { auto: true }],\n" +
|
|
111
160
|
"});\n" +
|
|
112
161
|
"\n" +
|
|
113
162
|
"export { expect };\n" +
|
|
114
163
|
"export type * from '@playwright/test';\n");
|
|
115
164
|
}
|
|
116
165
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
166
|
+
* Point each spec's Playwright import at the generated `fixtures.ts` and write
|
|
167
|
+
* it — port of `_apply_fixtures`. Only the module specifier is touched.
|
|
168
|
+
*
|
|
169
|
+
* DOM capture is always on, so fixtures are ALWAYS injected (unlike the previous
|
|
170
|
+
* auth-only behavior): each spec's `'@playwright/test'` import is rewritten to
|
|
171
|
+
* `'./fixtures'` and `fixtures.ts` is (re)written every run. `replaySession` only
|
|
172
|
+
* controls whether the generated module also replays the captured sessionStorage.
|
|
120
173
|
*
|
|
121
|
-
* The Python version globs `*.spec.ts` in `specDir`; the agent already
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* directory (equivalent behavior, no extra glob dependency).
|
|
174
|
+
* The Python version globs `*.spec.ts` in `specDir`; the agent already knows
|
|
175
|
+
* exactly which spec filenames it wrote for this job, so `specFilenames` is
|
|
176
|
+
* passed explicitly instead of re-scanning the directory.
|
|
125
177
|
*
|
|
126
178
|
* @param specDir The job's local workdir containing the spec files.
|
|
127
179
|
* @param specFilenames The spec filenames written into `specDir` for this job.
|
|
128
180
|
* @param sessionFile Absolute path to the `sessionStorage.json` snapshot embedded
|
|
129
181
|
* in the generated `fixtures.ts`.
|
|
130
|
-
* @param
|
|
182
|
+
* @param replaySession Whether sessionStorage replay is active for this run.
|
|
131
183
|
*/
|
|
132
|
-
function
|
|
133
|
-
const replacements =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
]
|
|
138
|
-
: [
|
|
139
|
-
["'./fixtures'", "'@playwright/test'"],
|
|
140
|
-
['"./fixtures"', '"@playwright/test"'],
|
|
141
|
-
];
|
|
184
|
+
function applyFixtures(specDir, specFilenames, sessionFile, replaySession) {
|
|
185
|
+
const replacements = [
|
|
186
|
+
["'@playwright/test'", "'./fixtures'"],
|
|
187
|
+
['"@playwright/test"', '"./fixtures"'],
|
|
188
|
+
];
|
|
142
189
|
for (const filename of specFilenames) {
|
|
143
190
|
const specPath = path.join(specDir, filename);
|
|
144
191
|
let text;
|
|
@@ -156,7 +203,5 @@ function applyAuthFixtures(specDir, specFilenames, sessionFile, enabled) {
|
|
|
156
203
|
fs.writeFileSync(specPath, newText, "utf-8");
|
|
157
204
|
}
|
|
158
205
|
}
|
|
159
|
-
|
|
160
|
-
fs.writeFileSync(path.join(specDir, "fixtures.ts"), authFixturesTs(sessionFile), "utf-8");
|
|
161
|
-
}
|
|
206
|
+
fs.writeFileSync(path.join(specDir, "fixtures.ts"), fixturesTs(sessionFile, replaySession), "utf-8");
|
|
162
207
|
}
|
package/dist/src/report.js
CHANGED
|
@@ -22,6 +22,10 @@ const ATTACHMENT_KIND_MAP = {
|
|
|
22
22
|
screenshot: "screenshot",
|
|
23
23
|
video: "video",
|
|
24
24
|
trace: "trace",
|
|
25
|
+
// DOM captured by the injected fixtures (see playwrightConfig.fixturesTs):
|
|
26
|
+
// raw page HTML + a distilled interactable-element inventory.
|
|
27
|
+
"qagent-dom-raw": "dom",
|
|
28
|
+
"qagent-dom-distilled": "dom-distilled",
|
|
25
29
|
};
|
|
26
30
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
27
31
|
/**
|