electron-playwright-cli 0.1.1 → 0.1.3

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 CHANGED
@@ -26,12 +26,43 @@ Create `.playwright/cli.config.json` in your project root:
26
26
 
27
27
  The `args` array is passed to Electron. Point it at your app's main process entry point.
28
28
 
29
+ The daemon shuts down automatically when the Electron app closes (via `close` or the app exiting on its own). The next command spawns a fresh daemon that reads the current config from disk, so config changes take effect immediately — no need to manually kill the daemon.
30
+
29
31
  Optional launch options:
30
32
  - `executablePath`: path to the Electron binary (defaults to the one in node_modules)
31
33
  - `cwd`: working directory for the Electron process
32
34
  - `env`: additional environment variables
33
35
  - `timeout`: launch timeout in milliseconds
34
36
 
37
+ ### App readiness
38
+
39
+ By default, the CLI waits for the `load` event and a paint frame before accepting commands. For apps with async rendering (React, etc.), the first screenshot may still capture a blank page. Use `readyCondition` to tell the CLI when your app is actually ready:
40
+
41
+ ```json
42
+ {
43
+ "browser": {
44
+ "launchOptions": {
45
+ "args": ["main.js"]
46
+ },
47
+ "readyCondition": {
48
+ "waitForSelector": "[data-testid='app-ready']",
49
+ "timeout": 10000
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Available options (all optional, can be combined):
56
+
57
+ | Option | Type | Description |
58
+ |---|---|---|
59
+ | `waitForSelector` | CSS selector | Waits for the element to be visible |
60
+ | `waitForFunction` | JS expression | Waits for the expression to return truthy (e.g. `"document.fonts.ready"`) |
61
+ | `waitForLoadState` | `"load"` \| `"domcontentloaded"` \| `"networkidle"` | Waits for the specified load state |
62
+ | `timeout` | number (ms) | Timeout for all conditions (default: 10000) |
63
+
64
+ A double `requestAnimationFrame` paint wait always runs after any conditions, ensuring pixels are rendered before the first command.
65
+
35
66
  ## Usage
36
67
 
37
68
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-playwright-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Playwright CLI with Electron app support",
5
5
  "keywords": [
6
6
  "playwright",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "license": "Apache-2.0",
28
28
  "scripts": {
29
- "check": "npm run fmt:check && npm run lint && npm run typecheck",
29
+ "check": "npm run fmt && npm run lint && npm run typecheck",
30
30
  "fmt": "oxfmt .",
31
31
  "fmt:check": "oxfmt --check .",
32
32
  "lint": "oxlint .",
package/playwright-cli.js CHANGED
@@ -128,8 +128,10 @@ async function connectToSocket(socketPath) {
128
128
  async function connectToDaemon(sessionName) {
129
129
  const socketPath = daemonSocketPath(sessionName);
130
130
 
131
- // try to connect to existing daemon
132
- if (await socketExists(socketPath)) {
131
+ // try to connect to existing daemon.
132
+ // on Windows, named pipes aren't detected by stat().isSocket(),
133
+ // so we always attempt a direct connection.
134
+ if (os.platform() === "win32" || (await socketExists(socketPath))) {
133
135
  try {
134
136
  const socket = await connectToSocket(socketPath);
135
137
  return new SocketSession(socket);
@@ -203,7 +205,7 @@ async function main() {
203
205
  const args = require("minimist")(argv);
204
206
 
205
207
  if (args.version || args.v) {
206
- const pkg = require("playwright/package.json");
208
+ const pkg = require("./package.json");
207
209
  console.log(pkg.version);
208
210
  process.exit(0);
209
211
  }
@@ -33,23 +33,22 @@ class ElectronContextFactory {
33
33
  await electronApp.firstWindow();
34
34
  }
35
35
 
36
- // wait for the page to finish loading before handing it off —
36
+ // wait for the page to be ready before handing it off —
37
37
  // without this, screenshots taken immediately can capture a blank page
38
38
  const firstPage = browserContext.pages()[0];
39
39
  if (firstPage) {
40
- await firstPage
41
- .waitForLoadState("domcontentloaded", { timeout: 10000 })
42
- .catch(() => {});
40
+ await this._waitForReady(firstPage);
43
41
  }
44
42
 
45
43
  electronApp.process().on("exit", () => {
46
- setTimeout(() => process.exit(0), 1000);
44
+ setTimeout(() => process.exit(0), 500);
47
45
  });
48
46
 
49
47
  return {
50
48
  browserContext,
51
49
  close: async () => {
52
50
  await electronApp.close().catch(() => {});
51
+ setTimeout(() => process.exit(0), 500);
53
52
  },
54
53
  };
55
54
  } catch (e) {
@@ -57,6 +56,57 @@ class ElectronContextFactory {
57
56
  throw e;
58
57
  }
59
58
  }
59
+
60
+ // waits for the app to be ready using either user-provided conditions
61
+ // from config.browser.readyCondition, or a sensible default.
62
+ //
63
+ // config example:
64
+ // "readyCondition": {
65
+ // "waitForLoadState": "load",
66
+ // "waitForSelector": "[data-testid='app-ready']",
67
+ // "waitForFunction": "document.fonts.ready",
68
+ // "timeout": 10000
69
+ // }
70
+ async _waitForReady(page) {
71
+ const ready = this.config.browser?.readyCondition ?? {};
72
+ const timeout = ready.timeout ?? 10000;
73
+
74
+ // if user provided custom conditions, use those
75
+ if (
76
+ ready.waitForLoadState ||
77
+ ready.waitForSelector ||
78
+ ready.waitForFunction
79
+ ) {
80
+ if (ready.waitForLoadState) {
81
+ await page
82
+ .waitForLoadState(ready.waitForLoadState, { timeout })
83
+ .catch(() => {});
84
+ }
85
+ if (ready.waitForSelector) {
86
+ await page
87
+ .waitForSelector(ready.waitForSelector, { state: "visible", timeout })
88
+ .catch(() => {});
89
+ }
90
+ if (ready.waitForFunction) {
91
+ await page
92
+ .waitForFunction(ready.waitForFunction, null, { timeout })
93
+ .catch(() => {});
94
+ }
95
+ } else {
96
+ // default: wait for load event (all resources loaded)
97
+ await page.waitForLoadState("load", { timeout }).catch(() => {});
98
+ }
99
+
100
+ // always wait for at least one paint frame to ensure pixels are rendered
101
+ await page
102
+ .evaluate(
103
+ () =>
104
+ new Promise((resolve) =>
105
+ requestAnimationFrame(() => requestAnimationFrame(resolve)),
106
+ ),
107
+ )
108
+ .catch(() => {});
109
+ }
60
110
  }
61
111
 
62
112
  module.exports = { ElectronContextFactory };
@@ -33,12 +33,41 @@ Create `.playwright/cli.config.json` in your project root:
33
33
 
34
34
  The `args` array is passed to Electron. Point it at your app's main process entry point.
35
35
 
36
+ The daemon shuts down automatically when the Electron app closes (via `close` or the app exiting on its own). The next command spawns a fresh daemon that reads the current config from disk, so config changes take effect immediately — no need to manually kill the daemon.
37
+
36
38
  Optional launch options:
37
39
  - `executablePath`: path to the Electron binary (defaults to the one in node_modules)
38
40
  - `cwd`: working directory for the Electron process
39
41
  - `env`: additional environment variables
40
42
  - `timeout`: launch timeout in milliseconds
41
43
 
44
+ ### App readiness
45
+
46
+ By default, the CLI waits for the `load` event and a paint frame before accepting commands. For apps with async rendering (React, etc.), use `readyCondition` to tell the CLI when your app is actually ready:
47
+
48
+ ```json
49
+ {
50
+ "browser": {
51
+ "launchOptions": {
52
+ "args": ["main.js"]
53
+ },
54
+ "readyCondition": {
55
+ "waitForSelector": "[data-testid='app-ready']",
56
+ "timeout": 10000
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ Available options (all optional, can be combined):
63
+
64
+ | Option | Type | Description |
65
+ |---|---|---|
66
+ | `waitForSelector` | CSS selector | Waits for the element to be visible |
67
+ | `waitForFunction` | JS expression | Waits for the expression to return truthy (e.g. `"document.fonts.ready"`) |
68
+ | `waitForLoadState` | `"load"` \| `"domcontentloaded"` \| `"networkidle"` | Waits for the specified load state |
69
+ | `timeout` | number (ms) | Timeout for all conditions (default: 10000) |
70
+
42
71
  ## Commands
43
72
 
44
73
  ### Core