@playwright-repl/runner 0.21.2

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.
Files changed (162) hide show
  1. package/README.md +116 -0
  2. package/dist/args.d.ts +16 -0
  3. package/dist/args.d.ts.map +1 -0
  4. package/dist/args.js +48 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/bridge-utils.cjs +197 -0
  7. package/dist/bridge-utils.cjs.map +1 -0
  8. package/dist/bridge-utils.d.cts +30 -0
  9. package/dist/bridge-utils.d.cts.map +1 -0
  10. package/dist/browser.d.ts +12 -0
  11. package/dist/browser.d.ts.map +1 -0
  12. package/dist/browser.js +40 -0
  13. package/dist/browser.js.map +1 -0
  14. package/dist/bundler.d.ts +6 -0
  15. package/dist/bundler.d.ts.map +1 -0
  16. package/dist/bundler.js +32 -0
  17. package/dist/bundler.js.map +1 -0
  18. package/dist/cdpPreload.cjs +44 -0
  19. package/dist/cdpPreload.cjs.map +1 -0
  20. package/dist/cdpPreload.d.cts +11 -0
  21. package/dist/cdpPreload.d.cts.map +1 -0
  22. package/dist/chrome-extension/background.js +233485 -0
  23. package/dist/chrome-extension/background.js.map +1 -0
  24. package/dist/chrome-extension/content/picker.js +279 -0
  25. package/dist/chrome-extension/content/recorder.js +475 -0
  26. package/dist/chrome-extension/devtools/console.html +17 -0
  27. package/dist/chrome-extension/devtools/console.js +28 -0
  28. package/dist/chrome-extension/devtools/console.js.map +1 -0
  29. package/dist/chrome-extension/devtools/devtools.html +8 -0
  30. package/dist/chrome-extension/devtools/devtools.js +7 -0
  31. package/dist/chrome-extension/devtools/devtools.js.map +1 -0
  32. package/dist/chrome-extension/icons/dramaturg_icon_128.png +0 -0
  33. package/dist/chrome-extension/icons/dramaturg_icon_16.png +0 -0
  34. package/dist/chrome-extension/icons/dramaturg_icon_32.png +0 -0
  35. package/dist/chrome-extension/icons/dramaturg_icon_48.png +0 -0
  36. package/dist/chrome-extension/index.css +1333 -0
  37. package/dist/chrome-extension/index.js +12462 -0
  38. package/dist/chrome-extension/index.js.map +1 -0
  39. package/dist/chrome-extension/index2.js +27327 -0
  40. package/dist/chrome-extension/index2.js.map +1 -0
  41. package/dist/chrome-extension/manifest.json +46 -0
  42. package/dist/chrome-extension/modulepreload-polyfill.js +30 -0
  43. package/dist/chrome-extension/modulepreload-polyfill.js.map +1 -0
  44. package/dist/chrome-extension/newtab/newtab.html +202 -0
  45. package/dist/chrome-extension/offscreen/offscreen.html +6 -0
  46. package/dist/chrome-extension/offscreen/offscreen.js +54 -0
  47. package/dist/chrome-extension/offscreen/offscreen.js.map +1 -0
  48. package/dist/chrome-extension/panel/panel.html +16 -0
  49. package/dist/chrome-extension/panel/panel.js +2210 -0
  50. package/dist/chrome-extension/panel/panel.js.map +1 -0
  51. package/dist/chrome-extension/preferences/preferences.html +14 -0
  52. package/dist/chrome-extension/preferences/preferences.js +102 -0
  53. package/dist/chrome-extension/preferences/preferences.js.map +1 -0
  54. package/dist/chrome-extension/settings.js +13 -0
  55. package/dist/chrome-extension/settings.js.map +1 -0
  56. package/dist/chrome-extension/sw-debugger-core.js +1127 -0
  57. package/dist/chrome-extension/sw-debugger-core.js.map +1 -0
  58. package/dist/cli.d.ts +14 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +43 -0
  61. package/dist/cli.js.map +1 -0
  62. package/dist/compiler/classify.d.ts +13 -0
  63. package/dist/compiler/classify.d.ts.map +1 -0
  64. package/dist/compiler/classify.js +143 -0
  65. package/dist/compiler/classify.js.map +1 -0
  66. package/dist/compiler/index.d.ts +24 -0
  67. package/dist/compiler/index.d.ts.map +1 -0
  68. package/dist/compiler/index.js +41 -0
  69. package/dist/compiler/index.js.map +1 -0
  70. package/dist/compiler/parser.d.ts +24 -0
  71. package/dist/compiler/parser.d.ts.map +1 -0
  72. package/dist/compiler/parser.js +77 -0
  73. package/dist/compiler/parser.js.map +1 -0
  74. package/dist/compiler/transform.d.ts +20 -0
  75. package/dist/compiler/transform.d.ts.map +1 -0
  76. package/dist/compiler/transform.js +36 -0
  77. package/dist/compiler/transform.js.map +1 -0
  78. package/dist/compiler.d.ts +24 -0
  79. package/dist/compiler.d.ts.map +1 -0
  80. package/dist/compiler.js +129 -0
  81. package/dist/compiler.js.map +1 -0
  82. package/dist/config.d.ts +6 -0
  83. package/dist/config.d.ts.map +1 -0
  84. package/dist/config.js +16 -0
  85. package/dist/config.js.map +1 -0
  86. package/dist/discover.d.ts +5 -0
  87. package/dist/discover.d.ts.map +1 -0
  88. package/dist/discover.js +46 -0
  89. package/dist/discover.js.map +1 -0
  90. package/dist/execute.d.ts +13 -0
  91. package/dist/execute.d.ts.map +1 -0
  92. package/dist/execute.js +281 -0
  93. package/dist/execute.js.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +2 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/mode-detect.d.ts +21 -0
  99. package/dist/mode-detect.d.ts.map +1 -0
  100. package/dist/mode-detect.js +67 -0
  101. package/dist/mode-detect.js.map +1 -0
  102. package/dist/proxy-page.d.ts +32 -0
  103. package/dist/proxy-page.d.ts.map +1 -0
  104. package/dist/proxy-page.js +192 -0
  105. package/dist/proxy-page.js.map +1 -0
  106. package/dist/pw-cli.d.ts +12 -0
  107. package/dist/pw-cli.d.ts.map +1 -0
  108. package/dist/pw-cli.js +73 -0
  109. package/dist/pw-cli.js.map +1 -0
  110. package/dist/pw-launch.d.ts +15 -0
  111. package/dist/pw-launch.d.ts.map +1 -0
  112. package/dist/pw-launch.js +97 -0
  113. package/dist/pw-launch.js.map +1 -0
  114. package/dist/pw-preload.cjs +164 -0
  115. package/dist/pw-preload.cjs.map +1 -0
  116. package/dist/pw-preload.d.cts +15 -0
  117. package/dist/pw-preload.d.cts.map +1 -0
  118. package/dist/pw-repl-extension.d.ts +12 -0
  119. package/dist/pw-repl-extension.d.ts.map +1 -0
  120. package/dist/pw-repl-extension.js +100 -0
  121. package/dist/pw-repl-extension.js.map +1 -0
  122. package/dist/pw-repl.d.ts +12 -0
  123. package/dist/pw-repl.d.ts.map +1 -0
  124. package/dist/pw-repl.js +111 -0
  125. package/dist/pw-repl.js.map +1 -0
  126. package/dist/pw-worker.cjs +216 -0
  127. package/dist/pw-worker.cjs.map +1 -0
  128. package/dist/pw-worker.d.cts +13 -0
  129. package/dist/pw-worker.d.cts.map +1 -0
  130. package/dist/pw-worker.d.ts +11 -0
  131. package/dist/pw-worker.d.ts.map +1 -0
  132. package/dist/pw-worker.js +51 -0
  133. package/dist/pw-worker.js.map +1 -0
  134. package/dist/run-test.d.ts +22 -0
  135. package/dist/run-test.d.ts.map +1 -0
  136. package/dist/run-test.js +358 -0
  137. package/dist/run-test.js.map +1 -0
  138. package/dist/runner.d.ts +10 -0
  139. package/dist/runner.d.ts.map +1 -0
  140. package/dist/runner.js +82 -0
  141. package/dist/runner.js.map +1 -0
  142. package/dist/shim/alias.d.ts +11 -0
  143. package/dist/shim/alias.d.ts.map +1 -0
  144. package/dist/shim/alias.js +24 -0
  145. package/dist/shim/alias.js.map +1 -0
  146. package/dist/shim/framework.d.ts +11 -0
  147. package/dist/shim/framework.d.ts.map +1 -0
  148. package/dist/shim/framework.js +11 -0
  149. package/dist/shim/framework.js.map +1 -0
  150. package/dist/shim/test-runner-node.d.ts +49 -0
  151. package/dist/shim/test-runner-node.d.ts.map +1 -0
  152. package/dist/shim/test-runner-node.js +191 -0
  153. package/dist/shim/test-runner-node.js.map +1 -0
  154. package/dist/shim/test-runner.d.ts +44 -0
  155. package/dist/shim/test-runner.d.ts.map +1 -0
  156. package/dist/shim/test-runner.js +181 -0
  157. package/dist/shim/test-runner.js.map +1 -0
  158. package/dist/types.d.ts +41 -0
  159. package/dist/types.d.ts.map +1 -0
  160. package/dist/types.js +2 -0
  161. package/dist/types.js.map +1 -0
  162. package/package.json +37 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Parser — finds all page.* and expect(page.*) calls in JavaScript AST.
3
+ *
4
+ * Uses acorn to parse JS (post-esbuild TS stripping) and walks the AST
5
+ * to find ExpressionStatements containing page-rooted calls.
6
+ */
7
+ import * as acorn from 'acorn';
8
+ import * as walk from 'acorn-walk';
9
+ /**
10
+ * Check if an expression is rooted at the `page` identifier.
11
+ * Walks down MemberExpression/CallExpression chains to find the root.
12
+ */
13
+ function getRoot(node) {
14
+ if (node.type === 'Identifier')
15
+ return node.name;
16
+ if (node.type === 'MemberExpression')
17
+ return getRoot(node.object);
18
+ if (node.type === 'CallExpression')
19
+ return getRoot(node.callee);
20
+ return null;
21
+ }
22
+ /**
23
+ * Check if a call expression is `expect(page.*)`.
24
+ */
25
+ function isExpectPageCall(node) {
26
+ if (node.type !== 'CallExpression')
27
+ return false;
28
+ const callee = node.callee;
29
+ // expect(page.locator(...)).toBeVisible() — callee is expect(page.*).toBeVisible
30
+ // Walk to find if root is expect() with page-rooted arg
31
+ if (callee.type === 'MemberExpression') {
32
+ const obj = callee.object;
33
+ if (obj.type === 'CallExpression' && getRoot(obj.callee) === 'expect') {
34
+ // Check if first arg is page-rooted
35
+ return obj.arguments.length > 0 && getRoot(obj.arguments[0]) === 'page';
36
+ }
37
+ // Deeper chain: expect(page.*).not.toBeVisible()
38
+ return isExpectPageCall(obj);
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * Parse JavaScript and find all page-rooted ExpressionStatements.
44
+ */
45
+ export function findPageCalls(code) {
46
+ const ast = acorn.parse(code, {
47
+ ecmaVersion: 'latest',
48
+ sourceType: 'module',
49
+ allowAwaitOutsideFunction: true,
50
+ });
51
+ const calls = [];
52
+ walk.ancestor(ast, {
53
+ ExpressionStatement(node, ancestors) {
54
+ const expr = node.expression;
55
+ // Must be: await <something>
56
+ if (expr.type !== 'AwaitExpression')
57
+ return;
58
+ const arg = expr.argument;
59
+ // Check if it's page-rooted or expect(page.*)-rooted
60
+ const root = getRoot(arg);
61
+ const isPage = root === 'page';
62
+ const isExpect = isExpectPageCall(arg);
63
+ if (!isPage && !isExpect)
64
+ return;
65
+ calls.push({
66
+ node,
67
+ awaitExpr: expr,
68
+ callExpr: arg,
69
+ start: node.start,
70
+ end: node.end,
71
+ ancestors: [...ancestors],
72
+ });
73
+ },
74
+ });
75
+ return calls;
76
+ }
77
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/compiler/parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAgBnC;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAS;IACxB,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IACjD,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClE,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,IAAS;IACjC,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,iFAAiF;IACjF,wDAAwD;IACxD,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;YACtE,oCAAoC;YACpC,OAAO,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;QAC1E,CAAC;QACD,iDAAiD;QACjD,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;QAC5B,WAAW,EAAE,QAAQ;QACrB,UAAU,EAAE,QAAQ;QACpB,yBAAyB,EAAE,IAAI;KAChC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACjB,mBAAmB,CAAC,IAAS,EAAE,SAAgB;YAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;YAE7B,6BAA6B;YAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB;gBAAE,OAAO;YAE5C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;YAE1B,qDAAqD;YACrD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC;YAC/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAEvC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;gBAAE,OAAO;YAEjC,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI;gBACJ,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,GAAG;gBACb,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;aAC1B,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Transform — rewrites bridge-classified calls to __bridge("...") calls.
3
+ *
4
+ * Uses magic-string for source-map-preserving replacements.
5
+ */
6
+ import MagicString from 'magic-string';
7
+ import type { PageCallNode } from './parser.js';
8
+ /**
9
+ * Rewrite bridge-classified calls in source code.
10
+ *
11
+ * Transforms:
12
+ * await page.click('#btn');
13
+ * To:
14
+ * await __bridge('await page.click("#btn")');
15
+ */
16
+ export declare function transformBridgeCalls(code: string, bridgeCalls: PageCallNode[]): {
17
+ code: string;
18
+ map: ReturnType<MagicString['generateMap']>;
19
+ };
20
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/compiler/transform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,YAAY,EAAE,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;CAAE,CAyB/D"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Transform — rewrites bridge-classified calls to __bridge("...") calls.
3
+ *
4
+ * Uses magic-string for source-map-preserving replacements.
5
+ */
6
+ import MagicString from 'magic-string';
7
+ /**
8
+ * Rewrite bridge-classified calls in source code.
9
+ *
10
+ * Transforms:
11
+ * await page.click('#btn');
12
+ * To:
13
+ * await __bridge('await page.click("#btn")');
14
+ */
15
+ export function transformBridgeCalls(code, bridgeCalls) {
16
+ const s = new MagicString(code);
17
+ for (const call of bridgeCalls) {
18
+ // Extract the original expression: "await page.click('#btn')"
19
+ // The node is the ExpressionStatement, which includes the trailing semicolon
20
+ const stmtText = code.slice(call.start, call.end);
21
+ // Strip trailing semicolon and whitespace for the bridge argument
22
+ const exprText = stmtText.replace(/;\s*$/, '').trim();
23
+ // Escape for string literal (single quotes in the expression become escaped)
24
+ const escaped = exprText
25
+ .replace(/\\/g, '\\\\')
26
+ .replace(/'/g, "\\'")
27
+ .replace(/\n/g, '\\n');
28
+ // Replace the entire statement
29
+ s.overwrite(call.start, call.end, `await __bridge('${escaped}');`);
30
+ }
31
+ return {
32
+ code: s.toString(),
33
+ map: s.generateMap({ hires: true }),
34
+ };
35
+ }
36
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/compiler/transform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,WAAW,MAAM,cAAc,CAAC;AAGvC;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,WAA2B;IAE3B,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;IAEhC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,8DAA8D;QAC9D,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAElD,kEAAkE;QAClE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAEtD,6EAA6E;QAC7E,MAAM,OAAO,GAAG,QAAQ;aACrB,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;aACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;aACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAEzB,+BAA+B;QAC/B,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,mBAAmB,OAAO,KAAK,CAAC,CAAC;IACrE,CAAC;IAED,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;QAClB,GAAG,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Compiler
3
+ *
4
+ * Transforms a test file for Node.js execution with bridge commands.
5
+ * Uses esbuild plugin to transform page and expect calls BEFORE compilation,
6
+ * so esbuild validates the transformed code.
7
+ *
8
+ * Flow:
9
+ * TS source → onLoad plugin (transform page/expect → bridge.run) → esbuild (compile + validate) → valid JS
10
+ */
11
+ /**
12
+ * Compile a test file for Node.js + bridge execution.
13
+ * Transforms page/expect calls to bridge.run() during compilation.
14
+ */
15
+ export declare function compileTestFile(testFilePath: string): Promise<string>;
16
+ /**
17
+ * Execute compiled test code in Node.js with bridge context.
18
+ * Writes to a temp .mjs file and dynamically imports it.
19
+ */
20
+ export declare function executeCompiledTest(compiledCode: string, bridgeRun: (command: string) => Promise<{
21
+ text?: string;
22
+ isError?: boolean;
23
+ }>): Promise<string>;
24
+ //# sourceMappingURL=compiler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH;;;GAGG;AACH,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D3E;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GAC5E,OAAO,CAAC,MAAM,CAAC,CAoBjB"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Compiler
3
+ *
4
+ * Transforms a test file for Node.js execution with bridge commands.
5
+ * Uses esbuild plugin to transform page and expect calls BEFORE compilation,
6
+ * so esbuild validates the transformed code.
7
+ *
8
+ * Flow:
9
+ * TS source → onLoad plugin (transform page/expect → bridge.run) → esbuild (compile + validate) → valid JS
10
+ */
11
+ import path from 'node:path';
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import { fileURLToPath } from 'node:url';
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ /**
17
+ * Compile a test file for Node.js + bridge execution.
18
+ * Transforms page/expect calls to bridge.run() during compilation.
19
+ */
20
+ export async function compileTestFile(testFilePath) {
21
+ const esbuild = await import('esbuild');
22
+ const shimPath = path.resolve(path.dirname(__filename), '../src/shim/test-runner-node.ts');
23
+ const testDir = path.dirname(testFilePath);
24
+ const testFileName = path.basename(testFilePath);
25
+ // Plugin: transforms test files and provides the entry wrapper
26
+ const bridgePlugin = {
27
+ name: 'bridge-transform',
28
+ setup(build) {
29
+ // Virtual entry that imports __runTests + the test file
30
+ build.onResolve({ filter: /^__test-entry__$/ }, () => ({
31
+ path: '__test-entry__',
32
+ namespace: 'bridge',
33
+ }));
34
+ build.onLoad({ filter: /.*/, namespace: 'bridge' }, () => ({
35
+ contents: `
36
+ import { __runTests } from '@playwright/test';
37
+ import './${testFileName}';
38
+ const __result = await __runTests();
39
+ export default __result;
40
+ `,
41
+ resolveDir: testDir,
42
+ loader: 'ts',
43
+ }));
44
+ // Transform .spec.ts / .test.ts files: page/expect → bridge.run()
45
+ build.onLoad({ filter: /\.(spec|test)\.(ts|js|mjs)$/ }, (args) => {
46
+ const source = fs.readFileSync(args.path, 'utf-8');
47
+ const transformed = transformSource(source);
48
+ return {
49
+ contents: transformed,
50
+ loader: args.path.endsWith('.ts') ? 'ts' : 'js',
51
+ resolveDir: path.dirname(args.path),
52
+ };
53
+ });
54
+ },
55
+ };
56
+ const result = await esbuild.build({
57
+ entryPoints: ['__test-entry__'],
58
+ bundle: true,
59
+ write: false,
60
+ format: 'esm',
61
+ platform: 'node',
62
+ sourcemap: 'inline', // source maps for Node.js debugger
63
+ plugins: [bridgePlugin],
64
+ alias: {
65
+ '@playwright/test': shimPath,
66
+ },
67
+ external: [
68
+ 'fs', 'path', 'child_process', 'os', 'crypto', 'util',
69
+ 'stream', 'events', 'net', 'http', 'https', 'url',
70
+ 'worker_threads', 'node:*',
71
+ ],
72
+ });
73
+ return result.outputFiles[0].text;
74
+ }
75
+ /**
76
+ * Execute compiled test code in Node.js with bridge context.
77
+ * Writes to a temp .mjs file and dynamically imports it.
78
+ */
79
+ export async function executeCompiledTest(compiledCode, bridgeRun) {
80
+ // Make bridge.run available as a global
81
+ globalThis.bridge = {
82
+ run: async (command) => {
83
+ const result = await bridgeRun(command);
84
+ if (result.isError)
85
+ throw new Error(result.text || 'Bridge command failed');
86
+ return result;
87
+ },
88
+ };
89
+ const tmpFile = path.join(os.tmpdir(), `pw-test-${Date.now()}.mjs`);
90
+ try {
91
+ fs.writeFileSync(tmpFile, compiledCode);
92
+ const module = await import(`file://${tmpFile.replace(/\\/g, '/')}`);
93
+ return typeof module.default === 'string' ? module.default : '(no output)';
94
+ }
95
+ finally {
96
+ delete globalThis.bridge;
97
+ try {
98
+ fs.unlinkSync(tmpFile);
99
+ }
100
+ catch { /* ignore */ }
101
+ }
102
+ }
103
+ // ─── Source Transform ──────────────────────────────────────────────────────
104
+ /**
105
+ * Transform test source: Node.js API calls → __node.invoke().
106
+ * page.*, expect(), locator — ALL stay untouched (run in browser).
107
+ * Only fs.*, Buffer.*, path.* get transformed.
108
+ */
109
+ function transformSource(source) {
110
+ return source.split('\n').map(line => {
111
+ const trimmed = line.trim();
112
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('import ') || trimmed.startsWith('export ')) {
113
+ return line;
114
+ }
115
+ return transformNodeCalls(line);
116
+ }).join('\n');
117
+ }
118
+ function transformNodeCalls(line) {
119
+ // fs.readFileSync('file', 'utf-8') → await __node.invoke('fs', 'readFileSync', ['file', 'utf-8'])
120
+ // fs.existsSync('file') → await __node.invoke('fs', 'existsSync', ['file'])
121
+ line = line.replace(/\bfs\.(\w+)\(([^)]*)\)/g, (_match, method, args) => `await __node.invoke('fs', '${method}', [${args}])`);
122
+ // Buffer.from(data, encoding) → await __node.invoke('Buffer', 'from', [data, encoding])
123
+ line = line.replace(/\bBuffer\.(\w+)\(([^)]*)\)/g, (_match, method, args) => `await __node.invoke('Buffer', '${method}', [${args}])`);
124
+ // path.resolve(...) → await __node.invoke('path', 'resolve', [...])
125
+ // path.join(...) → await __node.invoke('path', 'join', [...])
126
+ line = line.replace(/\bpath\.(\w+)\(([^)]*)\)/g, (_match, method, args) => `await __node.invoke('path', '${method}', [${args}])`);
127
+ return line;
128
+ }
129
+ //# sourceMappingURL=compiler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Load playwright.config.ts
3
+ */
4
+ import type { PlaywrightConfig } from './types.js';
5
+ export declare function loadConfig(configPath: string): Promise<PlaywrightConfig>;
6
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU9E"}
package/dist/config.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Load playwright.config.ts
3
+ */
4
+ import path from 'node:path';
5
+ export async function loadConfig(configPath) {
6
+ const absPath = path.resolve(configPath);
7
+ try {
8
+ const mod = await import(absPath);
9
+ return mod.default || mod;
10
+ }
11
+ catch {
12
+ // No config file — use defaults
13
+ return {};
14
+ }
15
+ }
16
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;QAChC,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Discover test files in the test directory.
3
+ */
4
+ export declare function discoverTests(testDir: string, filter?: string[]): string[];
5
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CA0B1E"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Discover test files in the test directory.
3
+ */
4
+ import path from 'node:path';
5
+ import fs from 'node:fs';
6
+ export function discoverTests(testDir, filter) {
7
+ const absDir = path.resolve(testDir);
8
+ if (!fs.existsSync(absDir)) {
9
+ return [];
10
+ }
11
+ // If filter specifies files or directories, use them
12
+ if (filter && filter.length > 0) {
13
+ const result = [];
14
+ for (const f of filter) {
15
+ const abs = path.resolve(f);
16
+ if (!fs.existsSync(abs))
17
+ continue;
18
+ if (fs.statSync(abs).isDirectory()) {
19
+ walkDir(abs, result);
20
+ }
21
+ else {
22
+ result.push(abs);
23
+ }
24
+ }
25
+ return result.sort();
26
+ }
27
+ // Walk directory for .spec.ts / .test.ts files
28
+ const files = [];
29
+ walkDir(absDir, files);
30
+ return files.sort();
31
+ }
32
+ function walkDir(dir, out) {
33
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
34
+ for (const entry of entries) {
35
+ if (entry.name === 'node_modules' || entry.name === 'playwright-tests' || entry.name.startsWith('.'))
36
+ continue;
37
+ const full = path.join(dir, entry.name);
38
+ if (entry.isDirectory()) {
39
+ walkDir(full, out);
40
+ }
41
+ else if (/\.(spec|test)\.(ts|js|mjs)$/.test(entry.name)) {
42
+ out.push(full);
43
+ }
44
+ }
45
+ }
46
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,MAAiB;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAErC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,qDAAqD;IACrD,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAClC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAED,+CAA+C;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,GAAa;IACzC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC/G,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,IAAI,6BAA6B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Execute a test file via two paths:
3
+ *
4
+ * 1. Browser path (default): compile → send to bridge → runs in service worker
5
+ * where page/expect are real Playwright objects. Zero bridge round-trips.
6
+ *
7
+ * 2. Node.js path: compile → run locally → page calls go through Proxy → bridge.
8
+ * Used when test imports Node.js APIs (fs, path, http, etc.)
9
+ */
10
+ import type { BridgeServer } from '@playwright-repl/core';
11
+ import type { RunOptions, TestResult } from './types.js';
12
+ export declare function executeTestFile(testFilePath: string, bridge: BridgeServer, opts: RunOptions, nodePage?: any, cdpPage?: any): Promise<TestResult[]>;
13
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqBzD,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,UAAU,EAChB,QAAQ,CAAC,EAAE,GAAG,EACd,OAAO,CAAC,EAAE,GAAG,GACZ,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB"}