@specwise/runner 0.1.0
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 +46 -0
- package/dist/api.d.ts +9 -0
- package/dist/api.js +33 -0
- package/dist/api.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.js +5 -0
- package/dist/log.js.map +1 -0
- package/dist/recorder.d.ts +29 -0
- package/dist/recorder.js +185 -0
- package/dist/recorder.js.map +1 -0
- package/dist/runner.d.ts +8 -0
- package/dist/runner.js +241 -0
- package/dist/runner.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @specwise/runner
|
|
2
|
+
|
|
3
|
+
Local test runner for [Specwise](https://specwise.wearesnx.studio) - executes test cases on your machine using Playwright.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Generate a token from your Specwise dashboard (Test Runs > Run Locally), then:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @specwise/runner --token=<token> --api=<api-url>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Options
|
|
14
|
+
|
|
15
|
+
| Flag | Description |
|
|
16
|
+
|------|-------------|
|
|
17
|
+
| `--token` | Execution token from Specwise (required) |
|
|
18
|
+
| `--api` | Specwise API base URL (required) |
|
|
19
|
+
| `--headed` | Show browser window (default: headless) |
|
|
20
|
+
| `--help` | Show help |
|
|
21
|
+
|
|
22
|
+
### Example
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @specwise/runner \
|
|
26
|
+
--token=eyJhbGciOiJIUzI1NiJ9... \
|
|
27
|
+
--api=https://api.specwise.wearesnx.studio
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
1. Pulls test cases and pipelines from the Specwise API
|
|
33
|
+
2. Records pipelines for test cases that don't have one yet
|
|
34
|
+
3. Executes each test case in a Chromium browser via Playwright
|
|
35
|
+
4. Captures screenshots at each step
|
|
36
|
+
5. Reports results back to the Specwise dashboard
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Node.js 18+
|
|
41
|
+
- Chromium is installed automatically via Playwright on first run
|
|
42
|
+
|
|
43
|
+
## Exit codes
|
|
44
|
+
|
|
45
|
+
- `0` - All tests passed
|
|
46
|
+
- `1` - One or more tests failed
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PullResponse, TestCaseReport } from "./types.js";
|
|
2
|
+
export declare class SpecwiseAPI {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
private token;
|
|
5
|
+
constructor(baseUrl: string, token: string);
|
|
6
|
+
private request;
|
|
7
|
+
pull(): Promise<PullResponse>;
|
|
8
|
+
report(results: TestCaseReport[]): Promise<void>;
|
|
9
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class SpecwiseAPI {
|
|
2
|
+
baseUrl;
|
|
3
|
+
token;
|
|
4
|
+
constructor(baseUrl, token) {
|
|
5
|
+
this.baseUrl = baseUrl;
|
|
6
|
+
this.token = token;
|
|
7
|
+
}
|
|
8
|
+
async request(method, path, body) {
|
|
9
|
+
const url = `${this.baseUrl}${path}`;
|
|
10
|
+
const res = await fetch(url, {
|
|
11
|
+
method,
|
|
12
|
+
headers: {
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
Authorization: `Bearer ${this.token}`,
|
|
15
|
+
},
|
|
16
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const text = await res.text().catch(() => "");
|
|
20
|
+
throw new Error(`API ${method} ${path} failed (${res.status}): ${text}`);
|
|
21
|
+
}
|
|
22
|
+
return res.json();
|
|
23
|
+
}
|
|
24
|
+
async pull() {
|
|
25
|
+
return this.request("POST", "/api/v1/test-execution/agent/pull");
|
|
26
|
+
}
|
|
27
|
+
async report(results) {
|
|
28
|
+
await this.request("POST", "/api/v1/test-execution/agent/report", {
|
|
29
|
+
results,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,WAAW;IAEZ;IACA;IAFV,YACU,OAAe,EACf,KAAa;QADb,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAQ;IACpB,CAAC;IAEI,KAAK,CAAC,OAAO,CACnB,MAAc,EACd,IAAY,EACZ,IAAc;QAEd,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM;YACN,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;aACtC;YACD,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC9C,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,OAAO,MAAM,IAAI,IAAI,YAAY,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,GAAG,CAAC,IAAI,EAAgB,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,IAAI,CAAC,OAAO,CAAe,MAAM,EAAE,mCAAmC,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAyB;QACpC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,qCAAqC,EAAE;YAChE,OAAO;SACR,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { SpecwiseAPI } from "./api.js";
|
|
3
|
+
import { executeTestRun } from "./runner.js";
|
|
4
|
+
import { log } from "./log.js";
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
let token = "";
|
|
7
|
+
let api = "";
|
|
8
|
+
let headed = false;
|
|
9
|
+
for (const arg of argv.slice(2)) {
|
|
10
|
+
if (arg.startsWith("--token=")) {
|
|
11
|
+
token = arg.slice("--token=".length);
|
|
12
|
+
}
|
|
13
|
+
else if (arg.startsWith("--api=")) {
|
|
14
|
+
api = arg.slice("--api=".length);
|
|
15
|
+
}
|
|
16
|
+
else if (arg === "--headed") {
|
|
17
|
+
headed = true;
|
|
18
|
+
}
|
|
19
|
+
else if (arg === "--help" || arg === "-h") {
|
|
20
|
+
console.log(`
|
|
21
|
+
@specwise/runner - Run Specwise tests locally with Playwright
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
npx @specwise/runner --token=<token> --api=<api_url>
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--token=<token> Execution token (from Specwise dashboard)
|
|
28
|
+
--api=<url> Specwise API URL (e.g. http://localhost:8000)
|
|
29
|
+
--headed Show browser window (default: headless)
|
|
30
|
+
--help Show this help
|
|
31
|
+
`);
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!token) {
|
|
36
|
+
console.error("Error: --token is required. Get one from the Specwise dashboard.");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (!api) {
|
|
40
|
+
console.error("Error: --api is required. e.g. --api=http://localhost:8000");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
return { token, api: api.replace(/\/$/, ""), headed };
|
|
44
|
+
}
|
|
45
|
+
async function main() {
|
|
46
|
+
const { token, api, headed } = parseArgs(process.argv);
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log(" \x1b[1m@specwise/runner\x1b[0m v0.1.0");
|
|
49
|
+
console.log(" ─────────────────────────");
|
|
50
|
+
console.log("");
|
|
51
|
+
const client = new SpecwiseAPI(api, token);
|
|
52
|
+
// Pull test data
|
|
53
|
+
log("Pulling test cases from Specwise...");
|
|
54
|
+
let data;
|
|
55
|
+
try {
|
|
56
|
+
data = await client.pull();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
log(`\x1b[31mFailed to pull test data: ${err instanceof Error ? err.message : err}\x1b[0m`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
log(`Project: ${data.project_name}`);
|
|
63
|
+
log(`Target: ${data.target_url}`);
|
|
64
|
+
log(`Test cases: ${data.test_cases.length}`);
|
|
65
|
+
if (data.test_cases.length === 0) {
|
|
66
|
+
log("No test cases to execute.");
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
// Resolve test account
|
|
70
|
+
const account = data.test_accounts[0] ?? null;
|
|
71
|
+
if (account) {
|
|
72
|
+
log(`Using account: ${account.label || account.email} (${account.role})`);
|
|
73
|
+
}
|
|
74
|
+
// Execute
|
|
75
|
+
const reports = await executeTestRun(data.test_cases, {
|
|
76
|
+
targetUrl: data.target_url,
|
|
77
|
+
account,
|
|
78
|
+
authConfig: data.auth_config,
|
|
79
|
+
}, !headed);
|
|
80
|
+
// Summary
|
|
81
|
+
const passed = reports.filter((r) => r.status === "passed").length;
|
|
82
|
+
const failed = reports.filter((r) => r.status === "failed").length;
|
|
83
|
+
const blocked = reports.filter((r) => r.status === "blocked").length;
|
|
84
|
+
const totalMs = reports.reduce((sum, r) => sum + r.duration_ms, 0);
|
|
85
|
+
console.log("");
|
|
86
|
+
console.log(" ─────────────────────────");
|
|
87
|
+
console.log(` \x1b[1mResults\x1b[0m`);
|
|
88
|
+
console.log(` \x1b[32m${passed} passed\x1b[0m \x1b[31m${failed} failed\x1b[0m ${blocked} blocked`);
|
|
89
|
+
console.log(` Total time: ${(totalMs / 1000).toFixed(1)}s`);
|
|
90
|
+
console.log("");
|
|
91
|
+
// Report back
|
|
92
|
+
log("Reporting results to Specwise...");
|
|
93
|
+
try {
|
|
94
|
+
await client.report(reports);
|
|
95
|
+
log("\x1b[32mResults reported successfully!\x1b[0m");
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
log(`\x1b[31mFailed to report results: ${err instanceof Error ? err.message : err}\x1b[0m`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// Exit with error code if any failed
|
|
102
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
103
|
+
}
|
|
104
|
+
main().catch((err) => {
|
|
105
|
+
console.error("\x1b[31mFatal error:\x1b[0m", err);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function log(msg: string): void;
|
package/dist/log.js
ADDED
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local pipeline recorder — uses the page DOM + AI (via Specwise API) to build pipelines.
|
|
3
|
+
*
|
|
4
|
+
* For now, this creates a best-effort pipeline from the test case steps
|
|
5
|
+
* using heuristic element matching. The server-side AI recorder is used
|
|
6
|
+
* when executing via the cloud path.
|
|
7
|
+
*
|
|
8
|
+
* The local recorder works without an AI API key by using smart selectors.
|
|
9
|
+
*/
|
|
10
|
+
import type { Page } from "playwright";
|
|
11
|
+
import type { AgentTestCase, ExecutionPipeline } from "./types.js";
|
|
12
|
+
interface RunContext {
|
|
13
|
+
targetUrl: string;
|
|
14
|
+
account: {
|
|
15
|
+
email: string;
|
|
16
|
+
password: string;
|
|
17
|
+
label: string;
|
|
18
|
+
role: string;
|
|
19
|
+
} | null;
|
|
20
|
+
authConfig: {
|
|
21
|
+
login_url: string;
|
|
22
|
+
auth_type: string;
|
|
23
|
+
email_field_selector: string;
|
|
24
|
+
password_field_selector: string;
|
|
25
|
+
submit_button_selector: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function recordPipeline(page: Page, testCase: AgentTestCase, ctx: RunContext): Promise<ExecutionPipeline>;
|
|
29
|
+
export {};
|
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local pipeline recorder — uses the page DOM + AI (via Specwise API) to build pipelines.
|
|
3
|
+
*
|
|
4
|
+
* For now, this creates a best-effort pipeline from the test case steps
|
|
5
|
+
* using heuristic element matching. The server-side AI recorder is used
|
|
6
|
+
* when executing via the cloud path.
|
|
7
|
+
*
|
|
8
|
+
* The local recorder works without an AI API key by using smart selectors.
|
|
9
|
+
*/
|
|
10
|
+
import { log } from "./log.js";
|
|
11
|
+
function makeStep(partial) {
|
|
12
|
+
return {
|
|
13
|
+
action: partial.action ?? "wait",
|
|
14
|
+
selector: partial.selector ?? "",
|
|
15
|
+
xpath: partial.xpath ?? "",
|
|
16
|
+
text: partial.text ?? "",
|
|
17
|
+
value: partial.value ?? "",
|
|
18
|
+
url: partial.url ?? "",
|
|
19
|
+
timeout: partial.timeout ?? 5000,
|
|
20
|
+
label: partial.label ?? "",
|
|
21
|
+
step_index: partial.step_index ?? 0,
|
|
22
|
+
description: partial.description ?? "",
|
|
23
|
+
key: partial.key ?? "",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function buildAuthSteps(ctx) {
|
|
27
|
+
if (!ctx.account || !ctx.authConfig.login_url)
|
|
28
|
+
return [];
|
|
29
|
+
const loginUrl = ctx.authConfig.login_url.startsWith("/")
|
|
30
|
+
? ctx.targetUrl.replace(/\/$/, "") + ctx.authConfig.login_url
|
|
31
|
+
: ctx.authConfig.login_url;
|
|
32
|
+
const steps = [
|
|
33
|
+
makeStep({
|
|
34
|
+
action: "navigate",
|
|
35
|
+
url: loginUrl,
|
|
36
|
+
label: "Navigate to login",
|
|
37
|
+
}),
|
|
38
|
+
makeStep({
|
|
39
|
+
action: "fill",
|
|
40
|
+
selector: ctx.authConfig.email_field_selector || 'input[type="email"], input[name="email"], input[name="username"]',
|
|
41
|
+
value: "{{account.email}}",
|
|
42
|
+
label: "Enter email",
|
|
43
|
+
step_index: 1,
|
|
44
|
+
}),
|
|
45
|
+
makeStep({
|
|
46
|
+
action: "fill",
|
|
47
|
+
selector: ctx.authConfig.password_field_selector || 'input[type="password"]',
|
|
48
|
+
value: "{{account.password}}",
|
|
49
|
+
label: "Enter password",
|
|
50
|
+
step_index: 2,
|
|
51
|
+
}),
|
|
52
|
+
makeStep({
|
|
53
|
+
action: "click",
|
|
54
|
+
selector: ctx.authConfig.submit_button_selector || 'button[type="submit"]',
|
|
55
|
+
text: "Sign in",
|
|
56
|
+
label: "Click login",
|
|
57
|
+
step_index: 3,
|
|
58
|
+
}),
|
|
59
|
+
makeStep({
|
|
60
|
+
action: "wait",
|
|
61
|
+
timeout: 2000,
|
|
62
|
+
label: "Wait for login",
|
|
63
|
+
step_index: 4,
|
|
64
|
+
}),
|
|
65
|
+
];
|
|
66
|
+
return steps;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Analyze a test step description and try to map it to pipeline actions.
|
|
70
|
+
* This is a heuristic approach — works well for common patterns.
|
|
71
|
+
*/
|
|
72
|
+
async function analyzeStep(page, action, expectedResult, testData, stepNumber) {
|
|
73
|
+
const steps = [];
|
|
74
|
+
const lowerAction = action.toLowerCase();
|
|
75
|
+
// Navigate patterns
|
|
76
|
+
if (lowerAction.includes("navigate") || lowerAction.includes("go to") || lowerAction.includes("open")) {
|
|
77
|
+
const urlMatch = action.match(/(?:to|open)\s+(https?:\/\/\S+|\/\S+)/i);
|
|
78
|
+
if (urlMatch) {
|
|
79
|
+
steps.push(makeStep({
|
|
80
|
+
action: "navigate",
|
|
81
|
+
url: urlMatch[1],
|
|
82
|
+
label: action,
|
|
83
|
+
step_index: stepNumber,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Click patterns
|
|
88
|
+
if (lowerAction.includes("click") || lowerAction.includes("press") || lowerAction.includes("tap") || lowerAction.includes("select")) {
|
|
89
|
+
// Try to extract what to click from the description
|
|
90
|
+
const buttonMatch = action.match(/click(?:\s+on)?\s+(?:the\s+)?["']?([^"']+?)["']?\s*(?:button|link|tab|menu|icon|element)?$/i)
|
|
91
|
+
|| action.match(/(?:press|tap)\s+(?:the\s+)?["']?([^"']+?)["']?/i);
|
|
92
|
+
const clickText = buttonMatch?.[1]?.trim() || "";
|
|
93
|
+
steps.push(makeStep({
|
|
94
|
+
action: "click",
|
|
95
|
+
text: clickText,
|
|
96
|
+
label: action,
|
|
97
|
+
step_index: stepNumber,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
// Fill / Enter / Type patterns
|
|
101
|
+
if (lowerAction.includes("enter") || lowerAction.includes("type") || lowerAction.includes("fill") || lowerAction.includes("input")) {
|
|
102
|
+
const fieldMatch = action.match(/(?:in(?:to)?|the)\s+["']?([^"']+?)["']?\s*(?:field|input|textbox|textarea)?$/i);
|
|
103
|
+
const selector = fieldMatch ? `[name="${fieldMatch[1]}"], [placeholder*="${fieldMatch[1]}" i], [aria-label*="${fieldMatch[1]}" i]` : "";
|
|
104
|
+
const value = testData || "test value";
|
|
105
|
+
steps.push(makeStep({
|
|
106
|
+
action: "fill",
|
|
107
|
+
selector,
|
|
108
|
+
value,
|
|
109
|
+
label: action,
|
|
110
|
+
step_index: stepNumber,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
// Verify / Assert patterns (from expected result)
|
|
114
|
+
if (expectedResult) {
|
|
115
|
+
const lowerExpected = expectedResult.toLowerCase();
|
|
116
|
+
if (lowerExpected.includes("displayed") || lowerExpected.includes("visible") || lowerExpected.includes("shown") || lowerExpected.includes("appears")) {
|
|
117
|
+
const textMatch = expectedResult.match(/["']([^"']+)["']/);
|
|
118
|
+
if (textMatch) {
|
|
119
|
+
steps.push(makeStep({
|
|
120
|
+
action: "assert_text",
|
|
121
|
+
selector: "body",
|
|
122
|
+
value: textMatch[1],
|
|
123
|
+
label: `Verify: ${expectedResult}`,
|
|
124
|
+
step_index: stepNumber,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (lowerExpected.includes("redirect") || lowerExpected.includes("navigate") || lowerExpected.includes("url")) {
|
|
129
|
+
const urlMatch = expectedResult.match(/(\/\S+|https?:\/\/\S+)/);
|
|
130
|
+
if (urlMatch) {
|
|
131
|
+
steps.push(makeStep({
|
|
132
|
+
action: "assert_url",
|
|
133
|
+
url: urlMatch[1],
|
|
134
|
+
label: `Verify URL: ${expectedResult}`,
|
|
135
|
+
step_index: stepNumber,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// If no patterns matched, add a generic wait + screenshot
|
|
141
|
+
if (steps.length === 0) {
|
|
142
|
+
steps.push(makeStep({
|
|
143
|
+
action: "wait",
|
|
144
|
+
timeout: 1000,
|
|
145
|
+
label: action,
|
|
146
|
+
step_index: stepNumber,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
// Always add a screenshot after each step group
|
|
150
|
+
steps.push(makeStep({
|
|
151
|
+
action: "screenshot",
|
|
152
|
+
label: `Step ${stepNumber}: ${action.slice(0, 50)}`,
|
|
153
|
+
step_index: stepNumber,
|
|
154
|
+
}));
|
|
155
|
+
return steps;
|
|
156
|
+
}
|
|
157
|
+
export async function recordPipeline(page, testCase, ctx) {
|
|
158
|
+
const authSteps = buildAuthSteps(ctx);
|
|
159
|
+
const testSteps = [];
|
|
160
|
+
// Navigate to target first
|
|
161
|
+
if (ctx.targetUrl) {
|
|
162
|
+
try {
|
|
163
|
+
await page.goto(ctx.targetUrl, { waitUntil: "domcontentloaded", timeout: 15000 });
|
|
164
|
+
await page.waitForTimeout(1000);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
log(` Warning: Could not navigate to ${ctx.targetUrl}: ${err}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Build pipeline from test case steps
|
|
171
|
+
for (const step of testCase.steps) {
|
|
172
|
+
const pipelineSteps = await analyzeStep(page, step.action, step.expected_result, step.test_data, step.step_number);
|
|
173
|
+
testSteps.push(...pipelineSteps);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
steps: testSteps,
|
|
177
|
+
auth_steps: authSteps,
|
|
178
|
+
recorded_at: new Date().toISOString(),
|
|
179
|
+
recorded_by: "local-agent",
|
|
180
|
+
target_url: ctx.targetUrl,
|
|
181
|
+
environment: "",
|
|
182
|
+
version: 1,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentTestCase, AuthConfig, TestAccount, TestCaseReport } from "./types.js";
|
|
2
|
+
interface RunContext {
|
|
3
|
+
targetUrl: string;
|
|
4
|
+
account: TestAccount | null;
|
|
5
|
+
authConfig: AuthConfig;
|
|
6
|
+
}
|
|
7
|
+
export declare function executeTestRun(testCases: AgentTestCase[], ctx: RunContext, headless: boolean): Promise<TestCaseReport[]>;
|
|
8
|
+
export {};
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
import { recordPipeline } from "./recorder.js";
|
|
4
|
+
function resolveTemplates(value, ctx) {
|
|
5
|
+
return value
|
|
6
|
+
.replace(/\{\{environment\.url\}\}/g, ctx.targetUrl)
|
|
7
|
+
.replace(/\{\{account\.email\}\}/g, ctx.account?.email ?? "")
|
|
8
|
+
.replace(/\{\{account\.password\}\}/g, ctx.account?.password ?? "")
|
|
9
|
+
.replace(/\{\{account\.label\}\}/g, ctx.account?.label ?? "")
|
|
10
|
+
.replace(/\{\{account\.role\}\}/g, ctx.account?.role ?? "");
|
|
11
|
+
}
|
|
12
|
+
function resolveStep(step, ctx) {
|
|
13
|
+
return {
|
|
14
|
+
...step,
|
|
15
|
+
selector: resolveTemplates(step.selector, ctx),
|
|
16
|
+
xpath: resolveTemplates(step.xpath, ctx),
|
|
17
|
+
text: resolveTemplates(step.text, ctx),
|
|
18
|
+
value: resolveTemplates(step.value, ctx),
|
|
19
|
+
url: resolveTemplates(step.url, ctx),
|
|
20
|
+
key: resolveTemplates(step.key, ctx),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function locateElement(page, step) {
|
|
24
|
+
if (step.selector) {
|
|
25
|
+
try {
|
|
26
|
+
const loc = page.locator(step.selector);
|
|
27
|
+
await loc.first().waitFor({ state: "visible", timeout: step.timeout });
|
|
28
|
+
return loc.first();
|
|
29
|
+
}
|
|
30
|
+
catch { /* fallthrough */ }
|
|
31
|
+
}
|
|
32
|
+
if (step.xpath) {
|
|
33
|
+
try {
|
|
34
|
+
const loc = page.locator(`xpath=${step.xpath}`);
|
|
35
|
+
await loc.first().waitFor({ state: "visible", timeout: step.timeout });
|
|
36
|
+
return loc.first();
|
|
37
|
+
}
|
|
38
|
+
catch { /* fallthrough */ }
|
|
39
|
+
}
|
|
40
|
+
if (step.text) {
|
|
41
|
+
try {
|
|
42
|
+
const loc = page.getByText(step.text, { exact: false });
|
|
43
|
+
await loc.first().waitFor({ state: "visible", timeout: step.timeout });
|
|
44
|
+
return loc.first();
|
|
45
|
+
}
|
|
46
|
+
catch { /* fallthrough */ }
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Element not found: CSS="${step.selector}" XPath="${step.xpath}" Text="${step.text}"`);
|
|
49
|
+
}
|
|
50
|
+
async function executeStep(page, step) {
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
const result = {
|
|
53
|
+
step_index: step.step_index,
|
|
54
|
+
action: step.action,
|
|
55
|
+
status: "passed",
|
|
56
|
+
actual_result: "",
|
|
57
|
+
error: null,
|
|
58
|
+
screenshot_b64: null,
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
switch (step.action) {
|
|
62
|
+
case "navigate":
|
|
63
|
+
await page.goto(step.url, { waitUntil: "domcontentloaded", timeout: step.timeout });
|
|
64
|
+
result.actual_result = `Navigated to ${step.url}`;
|
|
65
|
+
break;
|
|
66
|
+
case "fill": {
|
|
67
|
+
const el = await locateElement(page, step);
|
|
68
|
+
await el.fill(step.value);
|
|
69
|
+
result.actual_result = "Filled value";
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "click": {
|
|
73
|
+
const el = await locateElement(page, step);
|
|
74
|
+
await el.click({ timeout: step.timeout });
|
|
75
|
+
result.actual_result = "Clicked";
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "select_option": {
|
|
79
|
+
const el = await locateElement(page, step);
|
|
80
|
+
await el.selectOption(step.value, { timeout: step.timeout });
|
|
81
|
+
result.actual_result = `Selected: ${step.value}`;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "hover": {
|
|
85
|
+
const el = await locateElement(page, step);
|
|
86
|
+
await el.hover({ timeout: step.timeout });
|
|
87
|
+
result.actual_result = "Hovered";
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "press_key":
|
|
91
|
+
await page.keyboard.press(step.key);
|
|
92
|
+
result.actual_result = `Pressed: ${step.key}`;
|
|
93
|
+
break;
|
|
94
|
+
case "scroll":
|
|
95
|
+
await page.evaluate(() => window.scrollBy(0, 500));
|
|
96
|
+
result.actual_result = "Scrolled";
|
|
97
|
+
break;
|
|
98
|
+
case "wait":
|
|
99
|
+
await page.waitForTimeout(step.timeout);
|
|
100
|
+
result.actual_result = `Waited ${step.timeout}ms`;
|
|
101
|
+
break;
|
|
102
|
+
case "wait_for": {
|
|
103
|
+
await locateElement(page, step);
|
|
104
|
+
result.actual_result = "Element visible";
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "assert_visible": {
|
|
108
|
+
const el = await locateElement(page, step);
|
|
109
|
+
if (!(await el.isVisible())) {
|
|
110
|
+
throw new Error("Element not visible");
|
|
111
|
+
}
|
|
112
|
+
result.actual_result = "Visible";
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "assert_text": {
|
|
116
|
+
const el = await locateElement(page, step);
|
|
117
|
+
const text = (await el.textContent()) ?? "";
|
|
118
|
+
if (!text.includes(step.value)) {
|
|
119
|
+
throw new Error(`Expected "${step.value}" not in "${text.slice(0, 200)}"`);
|
|
120
|
+
}
|
|
121
|
+
result.actual_result = `Found: ${step.value}`;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case "assert_url": {
|
|
125
|
+
if (!page.url().includes(step.url)) {
|
|
126
|
+
throw new Error(`URL "${page.url()}" doesn't contain "${step.url}"`);
|
|
127
|
+
}
|
|
128
|
+
result.actual_result = `URL matches: ${page.url()}`;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case "screenshot":
|
|
132
|
+
result.actual_result = `Screenshot: ${step.label}`;
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
result.status = "skipped";
|
|
136
|
+
result.actual_result = `Unknown action: ${step.action}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
result.status = "failed";
|
|
141
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
142
|
+
result.actual_result = `Failed: ${result.error}`;
|
|
143
|
+
}
|
|
144
|
+
// Capture screenshot
|
|
145
|
+
try {
|
|
146
|
+
const buf = await page.screenshot({ type: "jpeg", quality: 70 });
|
|
147
|
+
result.screenshot_b64 = buf.toString("base64");
|
|
148
|
+
}
|
|
149
|
+
catch { /* ignore */ }
|
|
150
|
+
const ms = Date.now() - start;
|
|
151
|
+
const icon = result.status === "passed" ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
|
|
152
|
+
log(` ${icon} ${step.action} ${step.label || step.selector || step.url || ""} (${ms}ms)`);
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
async function runPipeline(page, pipeline, ctx) {
|
|
156
|
+
const stepResults = [];
|
|
157
|
+
let allPassed = true;
|
|
158
|
+
// Auth steps
|
|
159
|
+
for (const step of pipeline.auth_steps) {
|
|
160
|
+
const resolved = resolveStep(step, ctx);
|
|
161
|
+
const result = await executeStep(page, resolved);
|
|
162
|
+
stepResults.push(result);
|
|
163
|
+
if (result.status === "failed") {
|
|
164
|
+
allPassed = false;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Main steps (only if auth passed)
|
|
169
|
+
if (allPassed) {
|
|
170
|
+
for (const step of pipeline.steps) {
|
|
171
|
+
const resolved = resolveStep(step, ctx);
|
|
172
|
+
const result = await executeStep(page, resolved);
|
|
173
|
+
stepResults.push(result);
|
|
174
|
+
if (result.status === "failed") {
|
|
175
|
+
allPassed = false;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { status: allPassed ? "passed" : "failed", stepResults };
|
|
181
|
+
}
|
|
182
|
+
export async function executeTestRun(testCases, ctx, headless) {
|
|
183
|
+
const reports = [];
|
|
184
|
+
log("\nLaunching browser...");
|
|
185
|
+
const browser = await chromium.launch({ headless });
|
|
186
|
+
const context = await browser.newContext({
|
|
187
|
+
viewport: { width: 1280, height: 720 },
|
|
188
|
+
ignoreHTTPSErrors: true,
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
for (let i = 0; i < testCases.length; i++) {
|
|
192
|
+
const tc = testCases[i];
|
|
193
|
+
log(`\n[${i + 1}/${testCases.length}] ${tc.test_case_id}: ${tc.title}`);
|
|
194
|
+
const page = await context.newPage();
|
|
195
|
+
const start = Date.now();
|
|
196
|
+
let pipeline = tc.pipeline;
|
|
197
|
+
let recordedNewPipeline = false;
|
|
198
|
+
// Record pipeline if not available
|
|
199
|
+
if (!pipeline || tc.pipeline_status !== "recorded") {
|
|
200
|
+
log(" Recording pipeline with AI...");
|
|
201
|
+
try {
|
|
202
|
+
pipeline = await recordPipeline(page, tc, ctx);
|
|
203
|
+
recordedNewPipeline = true;
|
|
204
|
+
log(" Pipeline recorded successfully");
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
log(` \x1b[31mRecording failed: ${err instanceof Error ? err.message : err}\x1b[0m`);
|
|
208
|
+
reports.push({
|
|
209
|
+
test_case_id: tc.id,
|
|
210
|
+
status: "blocked",
|
|
211
|
+
step_results: [],
|
|
212
|
+
duration_ms: Date.now() - start,
|
|
213
|
+
pipeline_json: null,
|
|
214
|
+
});
|
|
215
|
+
await page.close();
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Execute pipeline
|
|
220
|
+
log(" Executing pipeline...");
|
|
221
|
+
const { status, stepResults } = await runPipeline(page, pipeline, ctx);
|
|
222
|
+
const duration = Date.now() - start;
|
|
223
|
+
const icon = status === "passed" ? "\x1b[32m✓ PASSED\x1b[0m" : "\x1b[31m✗ FAILED\x1b[0m";
|
|
224
|
+
log(` ${icon} (${duration}ms)`);
|
|
225
|
+
reports.push({
|
|
226
|
+
test_case_id: tc.id,
|
|
227
|
+
status,
|
|
228
|
+
step_results: stepResults,
|
|
229
|
+
duration_ms: duration,
|
|
230
|
+
pipeline_json: recordedNewPipeline ? pipeline : null,
|
|
231
|
+
});
|
|
232
|
+
await page.close();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
await context.close();
|
|
237
|
+
await browser.close();
|
|
238
|
+
}
|
|
239
|
+
return reports;
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface PullResponse {
|
|
2
|
+
run_id: string;
|
|
3
|
+
project_name: string;
|
|
4
|
+
target_url: string;
|
|
5
|
+
environment: string;
|
|
6
|
+
auth_config: AuthConfig;
|
|
7
|
+
test_accounts: TestAccount[];
|
|
8
|
+
test_cases: AgentTestCase[];
|
|
9
|
+
}
|
|
10
|
+
export interface AuthConfig {
|
|
11
|
+
login_url: string;
|
|
12
|
+
auth_type: string;
|
|
13
|
+
email_field_selector: string;
|
|
14
|
+
password_field_selector: string;
|
|
15
|
+
submit_button_selector: string;
|
|
16
|
+
success_indicator: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TestAccount {
|
|
19
|
+
label: string;
|
|
20
|
+
role: string;
|
|
21
|
+
email: string;
|
|
22
|
+
password: string;
|
|
23
|
+
notes: string;
|
|
24
|
+
environment: string;
|
|
25
|
+
}
|
|
26
|
+
export interface AgentTestCase {
|
|
27
|
+
id: string;
|
|
28
|
+
test_case_id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
preconditions: string;
|
|
32
|
+
steps: TestStep[];
|
|
33
|
+
pipeline: ExecutionPipeline | null;
|
|
34
|
+
pipeline_status: string;
|
|
35
|
+
}
|
|
36
|
+
export interface TestStep {
|
|
37
|
+
step_number: number;
|
|
38
|
+
action: string;
|
|
39
|
+
expected_result: string;
|
|
40
|
+
test_data: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ExecutionPipeline {
|
|
43
|
+
steps: PipelineStep[];
|
|
44
|
+
auth_steps: PipelineStep[];
|
|
45
|
+
recorded_at: string | null;
|
|
46
|
+
recorded_by: string;
|
|
47
|
+
target_url: string;
|
|
48
|
+
environment: string;
|
|
49
|
+
version: number;
|
|
50
|
+
}
|
|
51
|
+
export interface PipelineStep {
|
|
52
|
+
action: string;
|
|
53
|
+
selector: string;
|
|
54
|
+
xpath: string;
|
|
55
|
+
text: string;
|
|
56
|
+
value: string;
|
|
57
|
+
url: string;
|
|
58
|
+
timeout: number;
|
|
59
|
+
label: string;
|
|
60
|
+
step_index: number;
|
|
61
|
+
description: string;
|
|
62
|
+
key: string;
|
|
63
|
+
}
|
|
64
|
+
export interface StepResult {
|
|
65
|
+
step_index: number;
|
|
66
|
+
action: string;
|
|
67
|
+
status: "passed" | "failed" | "skipped";
|
|
68
|
+
actual_result: string;
|
|
69
|
+
error: string | null;
|
|
70
|
+
screenshot_b64: string | null;
|
|
71
|
+
}
|
|
72
|
+
export interface TestCaseReport {
|
|
73
|
+
test_case_id: string;
|
|
74
|
+
status: "passed" | "failed" | "blocked";
|
|
75
|
+
step_results: StepResult[];
|
|
76
|
+
duration_ms: number;
|
|
77
|
+
pipeline_json: ExecutionPipeline | null;
|
|
78
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@specwise/runner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local test runner for Specwise — executes test cases on your machine with Playwright",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"specwise-runner": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/cli.js",
|
|
14
|
+
"postinstall": "npx playwright install chromium --with-deps 2>/dev/null || true"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["specwise", "testing", "playwright", "automation"],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"playwright": "^1.49.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.5.0",
|
|
26
|
+
"@types/node": "^22.0.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
]
|
|
34
|
+
}
|