openspec-playwright 0.3.42 → 0.3.44
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 +57 -13
- package/dist/commands/doctor.js +69 -11
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/editors.d.ts +61 -12
- package/dist/commands/editors.js +316 -39
- package/dist/commands/editors.js.map +1 -1
- package/dist/commands/init.js +63 -30
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.js +25 -41
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +35 -30
- package/dist/commands/update.js.map +1 -1
- package/dist/shared/app-detect.d.ts +22 -0
- package/dist/shared/app-detect.js +210 -0
- package/dist/shared/app-detect.js.map +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/mcp.d.ts +15 -16
- package/dist/shared/mcp.js +30 -66
- package/dist/shared/mcp.js.map +1 -1
- package/package.json +6 -2
- package/templates/pages/BasePage.ts +2 -2
- package/templates/playwright.config.ts +105 -29
- package/templates/seed.spec.ts +3 -3
package/dist/shared/mcp.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Returns true if "playwright" appears in `claude mcp list` output.
|
|
2
|
+
* Shared MCP (Model Context Protocol) utilities.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Dispatches Playwright MCP install/remove to the right editor adapter.
|
|
5
|
+
* Each adapter handles its own install mechanism (`claude mcp add` vs
|
|
6
|
+
* editing `opencode.jsonc`), so this layer just routes the call and
|
|
7
|
+
* prints status messages.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export declare function removePlaywrightMcp(): void;
|
|
9
|
+
import { type EditorAdapter } from "../commands/editors.js";
|
|
10
|
+
/** Check if the named MCP server is installed in this editor. */
|
|
11
|
+
export declare function isMcpInstalled(adapter: EditorAdapter, serverName: string): boolean;
|
|
12
|
+
/** Install an MCP server in this editor. Throws on failure. */
|
|
13
|
+
export declare function ensureMcp(adapter: EditorAdapter, serverName: string, command: string[]): void;
|
|
14
|
+
/** Remove an MCP server from this editor. Does not throw if missing. */
|
|
15
|
+
export declare function removeMcp(adapter: EditorAdapter, serverName: string): void;
|
|
16
|
+
export declare function isPlaywrightMcpInstalled(adapter: EditorAdapter): boolean;
|
|
17
|
+
export declare function ensurePlaywrightMcp(adapter: EditorAdapter): void;
|
|
18
|
+
export declare function removePlaywrightMcp(adapter: EditorAdapter): void;
|
package/dist/shared/mcp.js
CHANGED
|
@@ -1,81 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
import { execFileSync } from "node:child_process";
|
|
6
|
-
import { TIMEOUT } from "./constants.js";
|
|
7
|
-
import { needsShell } from "./platform.js";
|
|
8
|
-
function outputIncludesPlaywright(output) {
|
|
9
|
-
return String(output ?? "").includes("playwright");
|
|
1
|
+
/** Check if the named MCP server is installed in this editor. */
|
|
2
|
+
export function isMcpInstalled(adapter, serverName) {
|
|
3
|
+
return adapter.isMcpInstalled(process.cwd(), serverName);
|
|
10
4
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Note: `claude mcp list` may exit non-zero if another MCP server is
|
|
16
|
-
* pending approval or unhealthy, while still printing the list. In that
|
|
17
|
-
* case, read stdout/stderr from the thrown error before returning false.
|
|
18
|
-
*/
|
|
19
|
-
export function isPlaywrightMcpInstalled() {
|
|
20
|
-
try {
|
|
21
|
-
const output = execFileSync("claude", ["mcp", "list"], {
|
|
22
|
-
encoding: "utf-8",
|
|
23
|
-
timeout: TIMEOUT.MCP_LIST,
|
|
24
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
25
|
-
shell: needsShell,
|
|
26
|
-
});
|
|
27
|
-
return outputIncludesPlaywright(output);
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
const e = err;
|
|
31
|
-
return outputIncludesPlaywright(e.stdout) || outputIncludesPlaywright(e.stderr);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Ensure Playwright MCP server is installed globally.
|
|
36
|
-
* Prints status messages. Throws on failure.
|
|
37
|
-
*/
|
|
38
|
-
export function ensurePlaywrightMcp() {
|
|
39
|
-
if (isPlaywrightMcpInstalled()) {
|
|
40
|
-
console.log(" ✓ Playwright MCP already installed");
|
|
5
|
+
/** Install an MCP server in this editor. Throws on failure. */
|
|
6
|
+
export function ensureMcp(adapter, serverName, command) {
|
|
7
|
+
if (isMcpInstalled(adapter, serverName)) {
|
|
8
|
+
console.log(` ✓ ${adapter.label}: ${serverName} MCP already installed`);
|
|
41
9
|
return;
|
|
42
10
|
}
|
|
43
11
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
timeout: TIMEOUT.MCP_LIST,
|
|
47
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
48
|
-
shell: needsShell,
|
|
49
|
-
});
|
|
50
|
-
console.log(" ✓ Playwright MCP installed globally");
|
|
12
|
+
adapter.installMcp(process.cwd(), serverName, command);
|
|
13
|
+
console.log(` ✓ ${adapter.label}: ${serverName} MCP installed`);
|
|
51
14
|
}
|
|
52
|
-
catch {
|
|
53
|
-
console.warn(
|
|
54
|
-
|
|
55
|
-
throw new Error("MCP installation failed");
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.warn(` ⚠ ${adapter.label}: failed to install ${serverName} MCP`);
|
|
17
|
+
throw err;
|
|
56
18
|
}
|
|
57
19
|
}
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export function removePlaywrightMcp() {
|
|
63
|
-
if (!isPlaywrightMcpInstalled()) {
|
|
64
|
-
console.log(" ✓ Playwright MCP not installed (nothing to remove)");
|
|
20
|
+
/** Remove an MCP server from this editor. Does not throw if missing. */
|
|
21
|
+
export function removeMcp(adapter, serverName) {
|
|
22
|
+
if (!isMcpInstalled(adapter, serverName)) {
|
|
23
|
+
console.log(` - ${adapter.label}: ${serverName} MCP not installed (nothing to remove)`);
|
|
65
24
|
return;
|
|
66
25
|
}
|
|
67
26
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
timeout: TIMEOUT.MCP_LIST,
|
|
71
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
-
shell: needsShell,
|
|
73
|
-
});
|
|
74
|
-
console.log(" ✓ Playwright MCP removed");
|
|
27
|
+
adapter.removeMcp(process.cwd(), serverName);
|
|
28
|
+
console.log(` ✓ ${adapter.label}: ${serverName} MCP removed`);
|
|
75
29
|
}
|
|
76
30
|
catch {
|
|
77
|
-
console.warn(
|
|
78
|
-
console.log(" Run manually: claude mcp remove playwright");
|
|
31
|
+
console.warn(` ⚠ ${adapter.label}: failed to remove ${serverName} MCP`);
|
|
79
32
|
}
|
|
80
33
|
}
|
|
34
|
+
// ─── Playwright MCP conveniences ────────────────────────────────────────
|
|
35
|
+
const PLAYWRIGHT_MCP_COMMAND = ["npx", "@playwright/mcp@latest"];
|
|
36
|
+
export function isPlaywrightMcpInstalled(adapter) {
|
|
37
|
+
return isMcpInstalled(adapter, "playwright");
|
|
38
|
+
}
|
|
39
|
+
export function ensurePlaywrightMcp(adapter) {
|
|
40
|
+
ensureMcp(adapter, "playwright", PLAYWRIGHT_MCP_COMMAND);
|
|
41
|
+
}
|
|
42
|
+
export function removePlaywrightMcp(adapter) {
|
|
43
|
+
removeMcp(adapter, "playwright");
|
|
44
|
+
}
|
|
81
45
|
//# sourceMappingURL=mcp.js.map
|
package/dist/shared/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/shared/mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/shared/mcp.ts"],"names":[],"mappings":"AAUA,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,OAAsB,EAAE,UAAkB;IACvE,OAAO,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;AAC3D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,SAAS,CACvB,OAAsB,EACtB,UAAkB,EAClB,OAAiB;IAEjB,IAAI,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,wBAAwB,CAAC,CAAC;QACzE,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,gBAAgB,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,KAAK,uBAAuB,UAAU,MAAM,CAAC,CAAC;QAC1E,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,OAAsB,EAAE,UAAkB;IAClE,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,wCAAwC,CAAC,CAAC;QACzF,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,cAAc,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,KAAK,sBAAsB,UAAU,MAAM,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,2EAA2E;AAE3E,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC;AAEjE,MAAM,UAAU,wBAAwB,CAAC,OAAsB;IAC7D,OAAO,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,SAAS,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openspec-playwright",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.44",
|
|
4
4
|
"description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
"chalk": "^5.3.0",
|
|
23
23
|
"commander": "^12.1.0",
|
|
24
24
|
"glob": "^13.0.6",
|
|
25
|
+
"jsonc-parser": "^3.3.1",
|
|
25
26
|
"playwright": "^1.50.0",
|
|
26
|
-
"tar": "^7.5.
|
|
27
|
+
"tar": "^7.5.16"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/glob": "^8.1.0",
|
|
@@ -37,6 +38,9 @@
|
|
|
37
38
|
"typescript": "^5.6.0",
|
|
38
39
|
"vitest": "^4.1.2"
|
|
39
40
|
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"vite": "^8.0.16"
|
|
43
|
+
},
|
|
40
44
|
"engines": {
|
|
41
45
|
"node": ">=20"
|
|
42
46
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Page, Locator, expect } from '@playwright/test';
|
|
6
6
|
|
|
7
|
-
const BASE_URL = process.env.BASE_URL
|
|
7
|
+
const BASE_URL = process.env.BASE_URL;
|
|
8
8
|
|
|
9
9
|
export class BasePage {
|
|
10
10
|
protected page: Page;
|
|
@@ -23,7 +23,7 @@ export class BasePage {
|
|
|
23
23
|
path: string,
|
|
24
24
|
options?: { waitUntil?: 'domcontentloaded' | 'load' | 'networkidle' | 'commit' },
|
|
25
25
|
) {
|
|
26
|
-
const url = path.startsWith('http') ? path :
|
|
26
|
+
const url = path.startsWith('http') || !BASE_URL ? path : new URL(path, BASE_URL).toString();
|
|
27
27
|
await this.page.goto(url, {
|
|
28
28
|
waitUntil: options?.waitUntil ?? 'domcontentloaded',
|
|
29
29
|
});
|
|
@@ -2,7 +2,7 @@ import { defineConfig, devices } from '@playwright/test';
|
|
|
2
2
|
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
|
-
// ─── Detect project root (where
|
|
5
|
+
// ─── Detect project root (where package.json lives) ───
|
|
6
6
|
function findProjectRoot(start: string): string {
|
|
7
7
|
let dir = start;
|
|
8
8
|
for (let i = 0; i < 10; i++) {
|
|
@@ -23,7 +23,8 @@ function findNpmRoot(projectRoot: string, maxDepth = 5): string {
|
|
|
23
23
|
if (existsSync(pkgPath)) {
|
|
24
24
|
try {
|
|
25
25
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
26
|
-
|
|
26
|
+
const scripts = pkg.scripts ?? {};
|
|
27
|
+
if (scripts['dev:all'] || scripts.dev || scripts.start || scripts.serve || scripts.preview) {
|
|
27
28
|
return dir;
|
|
28
29
|
}
|
|
29
30
|
} catch {}
|
|
@@ -41,41 +42,116 @@ function findNpmRoot(projectRoot: string, maxDepth = 5): string {
|
|
|
41
42
|
return search(projectRoot, 0) ?? projectRoot;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function parsePort(text: string): number | undefined {
|
|
46
|
+
const patterns = [
|
|
47
|
+
/(?:^|\s)(?:--port|-p)\s+([0-9]{2,5})(?:\s|$)/,
|
|
48
|
+
/(?:^|\s)--port=([0-9]{2,5})(?:\s|$)/,
|
|
49
|
+
/(?:^|\s)(?:PORT|VITE_PORT|PLAYWRIGHT_PORT|E2E_PORT)=([0-9]{2,5})(?:\s|$)/,
|
|
50
|
+
/port\s*:\s*([0-9]{2,5})/,
|
|
51
|
+
];
|
|
52
|
+
for (const pattern of patterns) {
|
|
53
|
+
const match = text.match(pattern);
|
|
54
|
+
if (match) {
|
|
55
|
+
const port = Number(match[1]);
|
|
56
|
+
if (port > 0 && port <= 65535) return port;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseEnvPort(content: string): number | undefined {
|
|
63
|
+
const lines = content.split(/\r?\n/);
|
|
64
|
+
for (const key of ['PLAYWRIGHT_PORT', 'E2E_PORT', 'VITE_PORT', 'PORT']) {
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const match = line.match(new RegExp(`^\\s*${key}\\s*=\\s*['\"]?([0-9]{2,5})['\"]?\\s*$`));
|
|
67
|
+
if (match) {
|
|
68
|
+
const port = Number(match[1]);
|
|
69
|
+
if (port > 0 && port <= 65535) return port;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function detectPortFromEnv(): number | undefined {
|
|
77
|
+
for (const key of ['PLAYWRIGHT_PORT', 'E2E_PORT', 'VITE_PORT', 'PORT']) {
|
|
78
|
+
const value = process.env[key];
|
|
79
|
+
if (!value) continue;
|
|
80
|
+
const port = Number(value);
|
|
81
|
+
if (Number.isInteger(port) && port > 0 && port <= 65535) return port;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function detectPortFromEnvFiles(npmRoot: string): number | undefined {
|
|
87
|
+
for (const file of ['.env.local', '.env.development', '.env']) {
|
|
88
|
+
const path = join(npmRoot, file);
|
|
89
|
+
if (!existsSync(path)) continue;
|
|
90
|
+
const port = parseEnvPort(readFileSync(path, 'utf-8'));
|
|
91
|
+
if (port) return port;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detectVitePort(npmRoot: string): number | undefined {
|
|
97
|
+
for (const file of ['vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs', 'vite.config.cjs']) {
|
|
98
|
+
const path = join(npmRoot, file);
|
|
99
|
+
if (!existsSync(path)) continue;
|
|
100
|
+
const port = parsePort(readFileSync(path, 'utf-8'));
|
|
101
|
+
if (port) return port;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function frameworkDefaultPort(pkg: Record<string, any>, command = ''): number | undefined {
|
|
107
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
108
|
+
if (command.includes('vite') || deps.vite) return 5173;
|
|
109
|
+
if (command.includes('astro') || deps.astro) return 4321;
|
|
110
|
+
if (command.includes('next') || deps.next) return 3000;
|
|
111
|
+
if (command.includes('nuxt') || deps.nuxt) return 3000;
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
44
115
|
const projectRoot = findProjectRoot(process.cwd());
|
|
45
116
|
const npmRoot = findNpmRoot(projectRoot);
|
|
117
|
+
const npmPkg = join(npmRoot, 'package.json');
|
|
118
|
+
const pkg = existsSync(npmPkg) ? JSON.parse(readFileSync(npmPkg, 'utf-8')) : {};
|
|
119
|
+
const scripts = pkg.scripts ?? {};
|
|
120
|
+
|
|
121
|
+
// ─── Dev command: detect from the npm project ───
|
|
122
|
+
const scriptName = scripts['dev:all']
|
|
123
|
+
? 'dev:all'
|
|
124
|
+
: scripts.dev
|
|
125
|
+
? 'dev'
|
|
126
|
+
: scripts.start
|
|
127
|
+
? 'start'
|
|
128
|
+
: scripts.serve
|
|
129
|
+
? 'serve'
|
|
130
|
+
: scripts.preview
|
|
131
|
+
? 'preview'
|
|
132
|
+
: 'dev';
|
|
133
|
+
let devCmd = `npm run ${scriptName}`;
|
|
134
|
+
if (npmRoot !== projectRoot) {
|
|
135
|
+
devCmd = `cd "${npmRoot}" && ${devCmd}`;
|
|
136
|
+
}
|
|
46
137
|
|
|
47
|
-
// ─── BASE_URL: prefer env, then seed.spec.ts, then default ───
|
|
138
|
+
// ─── BASE_URL: prefer env, then detected port, then seed.spec.ts, then default ───
|
|
48
139
|
const seedSpec = join(projectRoot, 'tests', 'playwright', 'seed.spec.ts');
|
|
49
|
-
let baseUrl = process.env.BASE_URL
|
|
50
|
-
if (!
|
|
140
|
+
let baseUrl = process.env.BASE_URL;
|
|
141
|
+
if (!baseUrl) {
|
|
142
|
+
const scriptPort = scripts[scriptName] ? parsePort(scripts[scriptName]) : undefined;
|
|
143
|
+
const port = detectPortFromEnv() ?? scriptPort ?? detectVitePort(npmRoot) ?? detectPortFromEnvFiles(npmRoot) ?? frameworkDefaultPort(pkg, scripts[scriptName]);
|
|
144
|
+
if (port) baseUrl = `http://localhost:${port}`;
|
|
145
|
+
}
|
|
146
|
+
if (!baseUrl && existsSync(seedSpec)) {
|
|
51
147
|
const content = readFileSync(seedSpec, 'utf-8');
|
|
52
148
|
const m = content.match(/BASE_URL\s*=\s*process\.env\.BASE_URL\s*\|\|\s*['"]([^'"]+)['"]/);
|
|
53
|
-
if (m)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// ─── Dev command: detect from the npm project ───
|
|
57
|
-
let devCmd = 'npm run dev';
|
|
58
|
-
const npmPkg = join(npmRoot, 'package.json');
|
|
59
|
-
if (existsSync(npmPkg)) {
|
|
60
|
-
const pkg = JSON.parse(readFileSync(npmPkg, 'utf-8'));
|
|
61
|
-
const scripts = pkg.scripts ?? {};
|
|
62
|
-
const scriptName = scripts['dev:all']
|
|
63
|
-
? 'dev:all'
|
|
64
|
-
: scripts.dev
|
|
65
|
-
? 'dev'
|
|
66
|
-
: scripts.start
|
|
67
|
-
? 'start'
|
|
68
|
-
: scripts.serve
|
|
69
|
-
? 'serve'
|
|
70
|
-
: scripts.preview
|
|
71
|
-
? 'preview'
|
|
72
|
-
: 'dev';
|
|
73
|
-
devCmd = `npm run ${scriptName}`;
|
|
74
|
-
// Prefix with cd if npmRoot differs from projectRoot
|
|
75
|
-
if (npmRoot !== projectRoot) {
|
|
76
|
-
devCmd = `cd ${npmRoot} && ${devCmd}`;
|
|
149
|
+
if (m) {
|
|
150
|
+
const candidate = m[1];
|
|
151
|
+
if (candidate.startsWith('http://') || candidate.startsWith('https://')) baseUrl = candidate;
|
|
77
152
|
}
|
|
78
153
|
}
|
|
154
|
+
baseUrl ??= 'http://localhost:3000';
|
|
79
155
|
|
|
80
156
|
const authStatePath = join(projectRoot, 'playwright', '.auth', 'user.json');
|
|
81
157
|
const storageState = existsSync(authStatePath) ? authStatePath : undefined;
|
package/templates/seed.spec.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { test, expect, Page, ConsoleMessage } from '@playwright/test';
|
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { BasePage } from './pages/BasePage';
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
const BASE_URL = process.env.BASE_URL || '
|
|
9
|
+
// Prefer Playwright config's use.baseURL; env var is still supported for one-off overrides.
|
|
10
|
+
const BASE_URL = process.env.BASE_URL || '/';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Page Object Pattern - extends BasePage for shared utilities
|
|
@@ -69,7 +69,7 @@ test.describe('Application smoke tests', () => {
|
|
|
69
69
|
|
|
70
70
|
test.describe('Environment validation', () => {
|
|
71
71
|
test('BASE_URL responds 200', async ({ page }) => {
|
|
72
|
-
const res = await page.request.get(
|
|
72
|
+
const res = await page.request.get(BASE_URL);
|
|
73
73
|
expect(res.status(), `BASE_URL ${BASE_URL} returned ${res.status()}`).toBeLessThan(500);
|
|
74
74
|
});
|
|
75
75
|
|