@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 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
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { SpecwiseAPI } from "./api.js";
2
+ export { executeTestRun } from "./runner.js";
3
+ export { recordPipeline } from "./recorder.js";
4
+ export type * from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { SpecwiseAPI } from "./api.js";
2
+ export { executeTestRun } from "./runner.js";
3
+ export { recordPipeline } from "./recorder.js";
4
+ //# sourceMappingURL=index.js.map
@@ -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
@@ -0,0 +1,5 @@
1
+ export function log(msg) {
2
+ const time = new Date().toLocaleTimeString("en-US", { hour12: false });
3
+ console.log(`[${time}] ${msg}`);
4
+ }
5
+ //# sourceMappingURL=log.js.map
@@ -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 {};
@@ -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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"}
@@ -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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }