@playwright/experimental-ct-core 1.41.0 → 1.41.1

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.
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.source = void 0;
7
+ const source = exports.source = "\"use strict\";\n\n// packages/playwright-ct-core/src/injected/importRegistry.ts\nfunction isImportRef(value) {\n return typeof value === \"object\" && value && value.__pw_type === \"importRef\";\n}\nvar ImportRegistry = class {\n constructor() {\n this._registry = /* @__PURE__ */ new Map();\n }\n initialize(components) {\n for (const [name, value] of Object.entries(components))\n this._registry.set(name, value);\n }\n async resolveImportRef(importRef) {\n const importFunction = this._registry.get(importRef.id);\n if (!importFunction)\n throw new Error(`Unregistered component: ${importRef.id}. Following components are registered: ${[...this._registry.keys()]}`);\n let importedObject = await importFunction();\n if (!importedObject)\n throw new Error(`Could not resolve component: ${importRef.id}.`);\n if (importRef.property) {\n importedObject = importedObject[importRef.property];\n if (!importedObject)\n throw new Error(`Could not instantiate component: ${importRef.id}.${importRef.property}.`);\n }\n return importedObject;\n }\n};\n\n// packages/playwright-ct-core/src/injected/serializers.ts\nfunction isFunctionRef(value) {\n return value && typeof value === \"object\" && value.__pw_type === \"function\";\n}\nasync function unwrapObject(value) {\n return transformObjectAsync(value, async (v) => {\n if (isFunctionRef(v)) {\n const result = (...args) => {\n window.__ctDispatchFunction(v.ordinal, args);\n };\n return { result };\n }\n if (isImportRef(v))\n return { result: await window.__pwRegistry.resolveImportRef(v) };\n });\n}\nfunction transformObject(value, mapping) {\n const result = mapping(value);\n if (result)\n return result.result;\n if (value === null || typeof value !== \"object\")\n return value;\n if (value instanceof Date || value instanceof RegExp || value instanceof URL)\n return value;\n if (Array.isArray(value)) {\n const result3 = [];\n for (const item of value)\n result3.push(transformObject(item, mapping));\n return result3;\n }\n const result2 = {};\n for (const [key, prop] of Object.entries(value))\n result2[key] = transformObject(prop, mapping);\n return result2;\n}\nasync function transformObjectAsync(value, mapping) {\n const result = await mapping(value);\n if (result)\n return result.result;\n if (value === null || typeof value !== \"object\")\n return value;\n if (value instanceof Date || value instanceof RegExp || value instanceof URL)\n return value;\n if (Array.isArray(value)) {\n const result3 = [];\n for (const item of value)\n result3.push(await transformObjectAsync(item, mapping));\n return result3;\n }\n const result2 = {};\n for (const [key, prop] of Object.entries(value))\n result2[key] = await transformObjectAsync(prop, mapping);\n return result2;\n}\n\n// packages/playwright-ct-core/src/injected/index.ts\nwindow.__pwRegistry = new ImportRegistry();\nwindow.__pwUnwrapObject = unwrapObject;\nwindow.__pwTransformObject = transformObject;\n";
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImportRegistry = void 0;
7
+ exports.isImportRef = isImportRef;
8
+ /**
9
+ * Copyright (c) Microsoft Corporation.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License.
22
+ */
23
+
24
+ function isImportRef(value) {
25
+ return typeof value === 'object' && value && value.__pw_type === 'importRef';
26
+ }
27
+ class ImportRegistry {
28
+ constructor() {
29
+ this._registry = new Map();
30
+ }
31
+ initialize(components) {
32
+ for (const [name, value] of Object.entries(components)) this._registry.set(name, value);
33
+ }
34
+ async resolveImportRef(importRef) {
35
+ const importFunction = this._registry.get(importRef.id);
36
+ if (!importFunction) throw new Error(`Unregistered component: ${importRef.id}. Following components are registered: ${[...this._registry.keys()]}`);
37
+ let importedObject = await importFunction();
38
+ if (!importedObject) throw new Error(`Could not resolve component: ${importRef.id}.`);
39
+ if (importRef.property) {
40
+ importedObject = importedObject[importRef.property];
41
+ if (!importedObject) throw new Error(`Could not instantiate component: ${importRef.id}.${importRef.property}.`);
42
+ }
43
+ return importedObject;
44
+ }
45
+ }
46
+ exports.ImportRegistry = ImportRegistry;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ var _importRegistry = require("./importRegistry");
4
+ var _serializers = require("./serializers");
5
+ /**
6
+ * Copyright (c) Microsoft Corporation.
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ window.__pwRegistry = new _importRegistry.ImportRegistry();
22
+ window.__pwUnwrapObject = _serializers.unwrapObject;
23
+ window.__pwTransformObject = _serializers.transformObject;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ // packages/playwright-ct-core/src/injected/importRegistry.ts
4
+ function isImportRef(value) {
5
+ return typeof value === "object" && value && value.__pw_type === "importRef";
6
+ }
7
+ var ImportRegistry = class {
8
+ constructor() {
9
+ this._registry = /* @__PURE__ */ new Map();
10
+ }
11
+ initialize(components) {
12
+ for (const [name, value] of Object.entries(components))
13
+ this._registry.set(name, value);
14
+ }
15
+ async resolveImportRef(importRef) {
16
+ const importFunction = this._registry.get(importRef.id);
17
+ if (!importFunction)
18
+ throw new Error(`Unregistered component: ${importRef.id}. Following components are registered: ${[...this._registry.keys()]}`);
19
+ let importedObject = await importFunction();
20
+ if (!importedObject)
21
+ throw new Error(`Could not resolve component: ${importRef.id}.`);
22
+ if (importRef.property) {
23
+ importedObject = importedObject[importRef.property];
24
+ if (!importedObject)
25
+ throw new Error(`Could not instantiate component: ${importRef.id}.${importRef.property}.`);
26
+ }
27
+ return importedObject;
28
+ }
29
+ };
30
+
31
+ // packages/playwright-ct-core/src/injected/serializers.ts
32
+ function isFunctionRef(value) {
33
+ return value && typeof value === "object" && value.__pw_type === "function";
34
+ }
35
+ async function unwrapObject(value) {
36
+ return transformObjectAsync(value, async (v) => {
37
+ if (isFunctionRef(v)) {
38
+ const result = (...args) => {
39
+ window.__ctDispatchFunction(v.ordinal, args);
40
+ };
41
+ return { result };
42
+ }
43
+ if (isImportRef(v))
44
+ return { result: await window.__pwRegistry.resolveImportRef(v) };
45
+ });
46
+ }
47
+ function transformObject(value, mapping) {
48
+ const result = mapping(value);
49
+ if (result)
50
+ return result.result;
51
+ if (value === null || typeof value !== "object")
52
+ return value;
53
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL)
54
+ return value;
55
+ if (Array.isArray(value)) {
56
+ const result3 = [];
57
+ for (const item of value)
58
+ result3.push(transformObject(item, mapping));
59
+ return result3;
60
+ }
61
+ const result2 = {};
62
+ for (const [key, prop] of Object.entries(value))
63
+ result2[key] = transformObject(prop, mapping);
64
+ return result2;
65
+ }
66
+ async function transformObjectAsync(value, mapping) {
67
+ const result = await mapping(value);
68
+ if (result)
69
+ return result.result;
70
+ if (value === null || typeof value !== "object")
71
+ return value;
72
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL)
73
+ return value;
74
+ if (Array.isArray(value)) {
75
+ const result3 = [];
76
+ for (const item of value)
77
+ result3.push(await transformObjectAsync(item, mapping));
78
+ return result3;
79
+ }
80
+ const result2 = {};
81
+ for (const [key, prop] of Object.entries(value))
82
+ result2[key] = await transformObjectAsync(prop, mapping);
83
+ return result2;
84
+ }
85
+
86
+ // packages/playwright-ct-core/src/injected/index.ts
87
+ window.__pwRegistry = new ImportRegistry();
88
+ window.__pwUnwrapObject = unwrapObject;
89
+ window.__pwTransformObject = transformObject;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.transformObject = transformObject;
7
+ exports.transformObjectAsync = transformObjectAsync;
8
+ exports.unwrapObject = unwrapObject;
9
+ exports.wrapObject = wrapObject;
10
+ var _importRegistry = require("./importRegistry");
11
+ /**
12
+ * Copyright (c) Microsoft Corporation.
13
+ *
14
+ * Licensed under the Apache License, Version 2.0 (the "License");
15
+ * you may not use this file except in compliance with the License.
16
+ * You may obtain a copy of the License at
17
+ *
18
+ * http://www.apache.org/licenses/LICENSE-2.0
19
+ *
20
+ * Unless required by applicable law or agreed to in writing, software
21
+ * distributed under the License is distributed on an "AS IS" BASIS,
22
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+ * See the License for the specific language governing permissions and
24
+ * limitations under the License.
25
+ */
26
+
27
+ function isFunctionRef(value) {
28
+ return value && typeof value === 'object' && value.__pw_type === 'function';
29
+ }
30
+ function wrapObject(value, callbacks) {
31
+ return transformObject(value, v => {
32
+ if (typeof v === 'function') {
33
+ const ordinal = callbacks.length;
34
+ callbacks.push(v);
35
+ const result = {
36
+ __pw_type: 'function',
37
+ ordinal
38
+ };
39
+ return {
40
+ result
41
+ };
42
+ }
43
+ });
44
+ }
45
+ async function unwrapObject(value) {
46
+ return transformObjectAsync(value, async v => {
47
+ if (isFunctionRef(v)) {
48
+ const result = (...args) => {
49
+ window.__ctDispatchFunction(v.ordinal, args);
50
+ };
51
+ return {
52
+ result
53
+ };
54
+ }
55
+ if ((0, _importRegistry.isImportRef)(v)) return {
56
+ result: await window.__pwRegistry.resolveImportRef(v)
57
+ };
58
+ });
59
+ }
60
+ function transformObject(value, mapping) {
61
+ const result = mapping(value);
62
+ if (result) return result.result;
63
+ if (value === null || typeof value !== 'object') return value;
64
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL) return value;
65
+ if (Array.isArray(value)) {
66
+ const result = [];
67
+ for (const item of value) result.push(transformObject(item, mapping));
68
+ return result;
69
+ }
70
+ const result2 = {};
71
+ for (const [key, prop] of Object.entries(value)) result2[key] = transformObject(prop, mapping);
72
+ return result2;
73
+ }
74
+ async function transformObjectAsync(value, mapping) {
75
+ const result = await mapping(value);
76
+ if (result) return result.result;
77
+ if (value === null || typeof value !== 'object') return value;
78
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL) return value;
79
+ if (Array.isArray(value)) {
80
+ const result = [];
81
+ for (const item of value) result.push(await transformObjectAsync(item, mapping));
82
+ return result;
83
+ }
84
+ const result2 = {};
85
+ for (const [key, prop] of Object.entries(value)) result2[key] = await transformObjectAsync(prop, mapping);
86
+ return result2;
87
+ }
package/lib/mount.js CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.fixtures = void 0;
7
+ var _serializers = require("./injected/serializers");
7
8
  /**
8
9
  * Copyright (c) Microsoft Corporation.
9
10
  *
@@ -35,7 +36,7 @@ const fixtures = exports.fixtures = {
35
36
  }, use, info) => {
36
37
  if (!info._configInternal.defineConfigWasUsed) throw new Error('Component testing requires the use of the defineConfig() in your playwright-ct.config.{ts,js}: https://aka.ms/playwright/ct-define-config');
37
38
  await page._wrapApiCall(async () => {
38
- await page.exposeFunction('__ct_dispatch', (ordinal, args) => {
39
+ await page.exposeFunction('__ctDispatchFunction', (ordinal, args) => {
39
40
  boundCallbacksForMount[ordinal](...args);
40
41
  });
41
42
  await page.goto(process.env.PLAYWRIGHT_TEST_BASE_URL);
@@ -45,9 +46,9 @@ const fixtures = exports.fixtures = {
45
46
  mount: async ({
46
47
  page
47
48
  }, use) => {
48
- await use(async (component, options) => {
49
+ await use(async (componentRef, options) => {
49
50
  const selector = await page._wrapApiCall(async () => {
50
- return await innerMount(page, component, options);
51
+ return await innerMount(page, componentRef, options);
51
52
  }, true);
52
53
  const locator = page.locator(selector);
53
54
  return Object.assign(locator, {
@@ -58,45 +59,31 @@ const fixtures = exports.fixtures = {
58
59
  });
59
60
  },
60
61
  update: async options => {
61
- if (isJsxApi(options)) return await innerUpdate(page, options);
62
- await innerUpdate(page, component, options);
62
+ if (isJsxComponent(options)) return await innerUpdate(page, options);
63
+ await innerUpdate(page, componentRef, options);
63
64
  }
64
65
  });
65
66
  });
66
67
  boundCallbacksForMount = [];
67
68
  }
68
69
  };
69
- function isJsxApi(options) {
70
- return (options === null || options === void 0 ? void 0 : options.kind) === 'jsx';
70
+ function isJsxComponent(component) {
71
+ return typeof component === 'object' && component && component.__pw_type === 'jsx';
71
72
  }
72
- async function innerUpdate(page, jsxOrType, options = {}) {
73
- const component = createComponent(jsxOrType, options);
74
- wrapFunctions(component, page, boundCallbacksForMount);
73
+ async function innerUpdate(page, componentRef, options = {}) {
74
+ const component = (0, _serializers.wrapObject)(createComponent(componentRef, options), boundCallbacksForMount);
75
75
  await page.evaluate(async ({
76
76
  component
77
77
  }) => {
78
- const unwrapFunctions = object => {
79
- for (const [key, value] of Object.entries(object)) {
80
- if (typeof value === 'string' && value.startsWith('__pw_func_')) {
81
- const ordinal = +value.substring('__pw_func_'.length);
82
- object[key] = (...args) => {
83
- window['__ct_dispatch'](ordinal, args);
84
- };
85
- } else if (typeof value === 'object' && value) {
86
- unwrapFunctions(value);
87
- }
88
- }
89
- };
90
- unwrapFunctions(component);
78
+ component = await window.__pwUnwrapObject(component);
91
79
  const rootElement = document.getElementById('root');
92
80
  return await window.playwrightUpdate(rootElement, component);
93
81
  }, {
94
82
  component
95
83
  });
96
84
  }
97
- async function innerMount(page, jsxOrType, options = {}) {
98
- const component = createComponent(jsxOrType, options);
99
- wrapFunctions(component, page, boundCallbacksForMount);
85
+ async function innerMount(page, componentRef, options = {}) {
86
+ const component = (0, _serializers.wrapObject)(createComponent(componentRef, options), boundCallbacksForMount);
100
87
 
101
88
  // WebKit does not wait for deferred scripts.
102
89
  await page.waitForFunction(() => !!window.playwrightMount);
@@ -104,19 +91,7 @@ async function innerMount(page, jsxOrType, options = {}) {
104
91
  component,
105
92
  hooksConfig
106
93
  }) => {
107
- const unwrapFunctions = object => {
108
- for (const [key, value] of Object.entries(object)) {
109
- if (typeof value === 'string' && value.startsWith('__pw_func_')) {
110
- const ordinal = +value.substring('__pw_func_'.length);
111
- object[key] = (...args) => {
112
- window['__ct_dispatch'](ordinal, args);
113
- };
114
- } else if (typeof value === 'object' && value) {
115
- unwrapFunctions(value);
116
- }
117
- }
118
- };
119
- unwrapFunctions(component);
94
+ component = await window.__pwUnwrapObject(component);
120
95
  let rootElement = document.getElementById('root');
121
96
  if (!rootElement) {
122
97
  rootElement = document.createElement('div');
@@ -131,24 +106,11 @@ async function innerMount(page, jsxOrType, options = {}) {
131
106
  });
132
107
  return selector;
133
108
  }
134
- function createComponent(jsxOrType, options = {}) {
135
- if (typeof jsxOrType !== 'string') return jsxOrType;
109
+ function createComponent(component, options = {}) {
110
+ if (component.__pw_type === 'jsx') return component;
136
111
  return {
137
- __pw_component_marker: true,
138
- kind: 'object',
139
- type: jsxOrType,
140
- options
112
+ __pw_type: 'object-component',
113
+ type: component,
114
+ ...options
141
115
  };
142
- }
143
- function wrapFunctions(object, page, callbacks) {
144
- for (const [key, value] of Object.entries(object)) {
145
- const type = typeof value;
146
- if (type === 'function') {
147
- const functionName = '__pw_func_' + callbacks.length;
148
- callbacks.push(value);
149
- object[key] = functionName;
150
- } else if (type === 'object' && value) {
151
- wrapFunctions(value, page, callbacks);
152
- }
153
- }
154
116
  }
@@ -3,12 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.collectComponentUsages = collectComponentUsages;
7
- exports.componentInfo = componentInfo;
8
6
  exports.default = void 0;
7
+ exports.importInfo = importInfo;
9
8
  var _path = _interopRequireDefault(require("path"));
10
9
  var _babelBundle = require("playwright/lib/transform/babelBundle");
11
10
  var _util = require("playwright/lib/util");
11
+ var _transform = require("playwright/lib/transform/transform");
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
13
  /**
14
14
  * Copyright (c) Microsoft Corporation.
@@ -27,160 +27,119 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
27
27
  */
28
28
 
29
29
  const t = _babelBundle.types;
30
- const fullNames = new Map();
31
- let componentNames;
32
- let componentIdentifiers;
30
+ let jsxComponentNames;
31
+ let importInfos;
33
32
  var _default = exports.default = (0, _babelBundle.declare)(api => {
34
33
  api.assertVersion(7);
35
34
  const result = {
36
35
  name: 'playwright-debug-transform',
37
36
  visitor: {
38
- Program(path) {
39
- fullNames.clear();
40
- const result = collectComponentUsages(path.node);
41
- componentNames = result.names;
42
- componentIdentifiers = result.identifiers;
37
+ Program: {
38
+ enter(path) {
39
+ jsxComponentNames = collectJsxComponentUsages(path.node);
40
+ importInfos = new Map();
41
+ },
42
+ exit(path) {
43
+ let firstDeclaration;
44
+ let lastImportDeclaration;
45
+ path.get('body').forEach(p => {
46
+ if (p.isImportDeclaration()) lastImportDeclaration = p;else if (!firstDeclaration) firstDeclaration = p;
47
+ });
48
+ const insertionPath = lastImportDeclaration || firstDeclaration;
49
+ if (!insertionPath) return;
50
+ for (const [localName, componentImport] of [...importInfos.entries()].reverse()) {
51
+ insertionPath.insertAfter(t.variableDeclaration('const', [t.variableDeclarator(t.identifier(localName), t.objectExpression([t.objectProperty(t.identifier('__pw_type'), t.stringLiteral('importRef')), t.objectProperty(t.identifier('id'), t.stringLiteral(componentImport.id))]))]));
52
+ }
53
+ (0, _transform.setTransformData)('playwright-ct-core', [...importInfos.values()]);
54
+ }
43
55
  },
44
56
  ImportDeclaration(p) {
45
57
  const importNode = p.node;
46
58
  if (!t.isStringLiteral(importNode.source)) return;
47
- let components = 0;
59
+ const ext = _path.default.extname(importNode.source.value);
60
+
61
+ // Convert all non-JS imports into refs.
62
+ if (!allJsExtensions.has(ext)) {
63
+ for (const specifier of importNode.specifiers) {
64
+ if (t.isImportNamespaceSpecifier(specifier)) continue;
65
+ const {
66
+ localName,
67
+ info
68
+ } = importInfo(importNode, specifier, this.filename);
69
+ importInfos.set(localName, info);
70
+ }
71
+ p.skip();
72
+ p.remove();
73
+ return;
74
+ }
75
+
76
+ // Convert JS imports that are used as components in JSX expressions into refs.
77
+ let importCount = 0;
48
78
  for (const specifier of importNode.specifiers) {
49
- const specifierName = specifier.local.name;
50
- const componentName = componentNames.has(specifierName) ? specifierName : [...componentNames].find(c => c.startsWith(specifierName + '.'));
51
- if (!componentName) continue;
52
79
  if (t.isImportNamespaceSpecifier(specifier)) continue;
53
80
  const {
54
- fullName
55
- } = componentInfo(specifier, importNode.source.value, this.filename, componentName);
56
- fullNames.set(componentName, fullName);
57
- ++components;
81
+ localName,
82
+ info
83
+ } = importInfo(importNode, specifier, this.filename);
84
+ if (jsxComponentNames.has(localName)) {
85
+ importInfos.set(localName, info);
86
+ ++importCount;
87
+ }
58
88
  }
59
89
 
60
- // All the imports were components => delete.
61
- if (components && components === importNode.specifiers.length) {
90
+ // All the imports were from JSX => delete.
91
+ if (importCount && importCount === importNode.specifiers.length) {
62
92
  p.skip();
63
93
  p.remove();
64
94
  }
65
95
  },
66
- Identifier(p) {
67
- if (componentIdentifiers.has(p.node)) {
68
- const componentName = fullNames.get(p.node.name) || p.node.name;
69
- p.replaceWith(t.stringLiteral(componentName));
70
- }
71
- },
72
- JSXElement(path) {
73
- const jsxElement = path.node;
74
- const jsxName = jsxElement.openingElement.name;
75
- let nameOrExpression = '';
76
- if (t.isJSXIdentifier(jsxName)) nameOrExpression = jsxName.name;else if (t.isJSXMemberExpression(jsxName) && t.isJSXIdentifier(jsxName.object) && t.isJSXIdentifier(jsxName.property)) nameOrExpression = jsxName.object.name + '.' + jsxName.property.name;
77
- if (!nameOrExpression) return;
78
- const componentName = fullNames.get(nameOrExpression) || nameOrExpression;
79
- const props = [];
80
- for (const jsxAttribute of jsxElement.openingElement.attributes) {
81
- if (t.isJSXAttribute(jsxAttribute)) {
82
- let namespace;
83
- let name;
84
- if (t.isJSXNamespacedName(jsxAttribute.name)) {
85
- namespace = jsxAttribute.name.namespace;
86
- name = jsxAttribute.name.name;
87
- } else if (t.isJSXIdentifier(jsxAttribute.name)) {
88
- name = jsxAttribute.name;
89
- }
90
- if (!name) continue;
91
- const attrName = (namespace ? namespace.name + ':' : '') + name.name;
92
- if (t.isStringLiteral(jsxAttribute.value)) props.push(t.objectProperty(t.stringLiteral(attrName), jsxAttribute.value));else if (t.isJSXExpressionContainer(jsxAttribute.value) && t.isExpression(jsxAttribute.value.expression)) props.push(t.objectProperty(t.stringLiteral(attrName), jsxAttribute.value.expression));else if (jsxAttribute.value === null) props.push(t.objectProperty(t.stringLiteral(attrName), t.booleanLiteral(true)));else props.push(t.objectProperty(t.stringLiteral(attrName), t.nullLiteral()));
93
- } else if (t.isJSXSpreadAttribute(jsxAttribute)) {
94
- props.push(t.spreadElement(jsxAttribute.argument));
95
- }
96
- }
97
- const children = [];
98
- for (const child of jsxElement.children) {
99
- if (t.isJSXText(child)) children.push(t.stringLiteral(child.value));else if (t.isJSXElement(child)) children.push(child);else if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) children.push(child.expression);else if (t.isJSXSpreadChild(child)) children.push(t.spreadElement(child.expression));
100
- }
101
- const component = [t.objectProperty(t.identifier('__pw_component_marker'), t.booleanLiteral(true)), t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')), t.objectProperty(t.identifier('type'), t.stringLiteral(componentName)), t.objectProperty(t.identifier('props'), t.objectExpression(props))];
102
- if (children.length) component.push(t.objectProperty(t.identifier('children'), t.arrayExpression(children)));
103
- path.replaceWith(t.objectExpression(component));
96
+ MemberExpression(path) {
97
+ if (!t.isIdentifier(path.node.object)) return;
98
+ if (!importInfos.has(path.node.object.name)) return;
99
+ if (!t.isIdentifier(path.node.property)) return;
100
+ path.replaceWith(t.objectExpression([t.spreadElement(t.identifier(path.node.object.name)), t.objectProperty(t.identifier('property'), t.stringLiteral(path.node.property.name))]));
104
101
  }
105
102
  }
106
103
  };
107
104
  return result;
108
105
  });
109
- function collectComponentUsages(node) {
110
- const importedLocalNames = new Set();
106
+ function collectJsxComponentUsages(node) {
111
107
  const names = new Set();
112
- const identifiers = new Set();
113
108
  (0, _babelBundle.traverse)(node, {
114
109
  enter: p => {
115
- // First look at all the imports.
116
- if (t.isImportDeclaration(p.node)) {
117
- const importNode = p.node;
118
- if (!t.isStringLiteral(importNode.source)) return;
119
- for (const specifier of importNode.specifiers) {
120
- if (t.isImportNamespaceSpecifier(specifier)) continue;
121
- importedLocalNames.add(specifier.local.name);
122
- }
123
- }
124
-
125
110
  // Treat JSX-everything as component usages.
126
111
  if (t.isJSXElement(p.node)) {
127
112
  if (t.isJSXIdentifier(p.node.openingElement.name)) names.add(p.node.openingElement.name.name);
128
- if (t.isJSXMemberExpression(p.node.openingElement.name) && t.isJSXIdentifier(p.node.openingElement.name.object) && t.isJSXIdentifier(p.node.openingElement.name.property)) names.add(p.node.openingElement.name.object.name + '.' + p.node.openingElement.name.property.name);
129
- }
130
-
131
- // Treat mount(identifier, ...) as component usage if it is in the importedLocalNames list.
132
- if (t.isAwaitExpression(p.node) && t.isCallExpression(p.node.argument) && t.isIdentifier(p.node.argument.callee) && p.node.argument.callee.name === 'mount') {
133
- const callExpression = p.node.argument;
134
- const arg = callExpression.arguments[0];
135
- if (!t.isIdentifier(arg) || !importedLocalNames.has(arg.name)) return;
136
- names.add(arg.name);
137
- identifiers.add(arg);
113
+ if (t.isJSXMemberExpression(p.node.openingElement.name) && t.isJSXIdentifier(p.node.openingElement.name.object) && t.isJSXIdentifier(p.node.openingElement.name.property)) names.add(p.node.openingElement.name.object.name);
138
114
  }
139
115
  }
140
116
  });
141
- return {
142
- names,
143
- identifiers
144
- };
117
+ return names;
145
118
  }
146
- function componentInfo(specifier, importSource, filename, componentName) {
119
+ function importInfo(importNode, specifier, filename) {
120
+ const importSource = importNode.source.value;
147
121
  const isModuleOrAlias = !importSource.startsWith('.');
148
122
  const unresolvedImportPath = _path.default.resolve(_path.default.dirname(filename), importSource);
149
123
  // Support following notations for Button.tsx:
150
124
  // - import { Button } from './Button.js' - via resolveImportSpecifierExtension
151
125
  // - import { Button } from './Button' - via require.resolve
152
126
  const importPath = isModuleOrAlias ? importSource : (0, _util.resolveImportSpecifierExtension)(unresolvedImportPath) || require.resolve(unresolvedImportPath);
153
- const prefix = importPath.replace(/[^\w_\d]/g, '_');
154
- const pathInfo = {
127
+ const idPrefix = importPath.replace(/[^\w_\d]/g, '_');
128
+ const result = {
129
+ id: idPrefix,
155
130
  importPath,
156
- isModuleOrAlias
131
+ isModuleOrAlias,
132
+ remoteName: undefined
157
133
  };
158
- const specifierName = specifier.local.name;
159
- let fullNameSuffix = '';
160
- let importedNameProperty = '';
161
- if (componentName !== specifierName) {
162
- const suffix = componentName.substring(specifierName.length + 1);
163
- fullNameSuffix = '_' + suffix;
164
- importedNameProperty = '.' + suffix;
134
+ if (t.isImportDefaultSpecifier(specifier)) {} else if (t.isIdentifier(specifier.imported)) {
135
+ result.remoteName = specifier.imported.name;
136
+ } else {
137
+ result.remoteName = specifier.imported.value;
165
138
  }
166
- if (t.isImportDefaultSpecifier(specifier)) return {
167
- fullName: prefix + fullNameSuffix,
168
- importedNameProperty,
169
- deps: [],
170
- ...pathInfo
171
- };
172
- if (t.isIdentifier(specifier.imported)) return {
173
- fullName: prefix + '_' + specifier.imported.name + fullNameSuffix,
174
- importedName: specifier.imported.name,
175
- importedNameProperty,
176
- deps: [],
177
- ...pathInfo
178
- };
139
+ if (result.remoteName) result.id += '_' + result.remoteName;
179
140
  return {
180
- fullName: prefix + '_' + specifier.imported.value + fullNameSuffix,
181
- importedName: specifier.imported.value,
182
- importedNameProperty,
183
- deps: [],
184
- ...pathInfo
141
+ localName: specifier.local.name,
142
+ info: result
185
143
  };
186
- }
144
+ }
145
+ const allJsExtensions = new Set(['.js', '.jsx', '.cjs', '.mjs', '.ts', '.tsx', '.cts', '.mts', '']);
package/lib/vitePlugin.js CHANGED
@@ -7,12 +7,11 @@ exports.createPlugin = createPlugin;
7
7
  var _utilsBundle = require("playwright-core/lib/utilsBundle");
8
8
  var _fs = _interopRequireDefault(require("fs"));
9
9
  var _path = _interopRequireDefault(require("path"));
10
- var _babelBundle = require("playwright/lib/transform/babelBundle");
11
10
  var _utilsBundle2 = require("playwright/lib/utilsBundle");
12
11
  var _utils = require("playwright-core/lib/utils");
13
12
  var _compilationCache = require("playwright/lib/transform/compilationCache");
14
- var _tsxTransform = require("./tsxTransform");
15
13
  var _vite = require("vite");
14
+ var _indexSource = require("./generated/indexSource");
16
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
16
  /**
18
17
  * Copyright (c) Microsoft Corporation.
@@ -88,7 +87,7 @@ function createPlugin(registerSourceFile, frameworkPluginFactory) {
88
87
  const buildInfoFile = _path.default.join(baseAndUserConfig.build.outDir, 'metainfo.json');
89
88
  let buildExists = false;
90
89
  let buildInfo;
91
- const registerSource = await _fs.default.promises.readFile(registerSourceFile, 'utf-8');
90
+ const registerSource = _indexSource.source + '\n' + (await _fs.default.promises.readFile(registerSourceFile, 'utf-8'));
92
91
  const registerSourceHash = (0, _utils.calculateSha1)(registerSource);
93
92
  try {
94
93
  buildInfo = JSON.parse(await _fs.default.promises.readFile(buildInfoFile, 'utf-8'));
@@ -102,15 +101,15 @@ function createPlugin(registerSourceFile, frameworkPluginFactory) {
102
101
  viteVersion: _vite.version,
103
102
  registerSourceHash,
104
103
  components: [],
105
- tests: {},
106
- sources: {}
104
+ sources: {},
105
+ deps: {}
107
106
  };
108
107
  }
109
108
  log('build exists:', buildExists);
110
109
  const componentRegistry = new Map();
111
- // 1. Re-parse changed tests and collect required components.
112
- const hasNewTests = await checkNewTests(suite, buildInfo, componentRegistry);
113
- log('has new tests:', hasNewTests);
110
+ const componentsByImportingFile = new Map();
111
+ // 1. Populate component registry based on tests' component imports.
112
+ await populateComponentsFromTests(componentRegistry, componentsByImportingFile);
114
113
 
115
114
  // 2. Check if the set of required components has changed.
116
115
  const hasNewComponents = await checkNewComponents(buildInfo, componentRegistry);
@@ -149,7 +148,8 @@ function createPlugin(registerSourceFile, frameworkPluginFactory) {
149
148
  if (frameworkPluginFactory && !((_baseAndUserConfig$pl = baseAndUserConfig.plugins) !== null && _baseAndUserConfig$pl !== void 0 && _baseAndUserConfig$pl.length)) frameworkOverrides.plugins = [await frameworkPluginFactory()];
150
149
 
151
150
  // But only add out own plugin when we actually build / transform.
152
- if (sourcesDirty) frameworkOverrides.plugins.push(vitePlugin(registerSource, templateDir, buildInfo, componentRegistry));
151
+ const depsCollector = new Map();
152
+ if (sourcesDirty) frameworkOverrides.plugins.push(vitePlugin(registerSource, templateDir, buildInfo, componentRegistry, depsCollector));
153
153
  frameworkOverrides.build = {
154
154
  target: 'esnext',
155
155
  minify: false,
@@ -166,18 +166,28 @@ function createPlugin(registerSourceFile, frameworkPluginFactory) {
166
166
  log('build');
167
167
  await (0, _vite.build)(finalConfig);
168
168
  await _fs.default.promises.rename(`${finalConfig.build.outDir}/${relativeTemplateDir}/index.html`, `${finalConfig.build.outDir}/index.html`);
169
+ buildInfo.deps = Object.fromEntries(depsCollector.entries());
169
170
  }
170
- if (hasNewTests || hasNewComponents || sourcesDirty) {
171
+ if (hasNewComponents || sourcesDirty) {
171
172
  log('write manifest');
172
173
  await _fs.default.promises.writeFile(buildInfoFile, JSON.stringify(buildInfo, undefined, 2));
173
174
  }
174
- for (const [filename, testInfo] of Object.entries(buildInfo.tests)) {
175
- const deps = new Set();
176
- for (const componentName of testInfo.components) {
177
- const component = componentRegistry.get(componentName);
178
- component === null || component === void 0 ? void 0 : component.deps.forEach(d => deps.add(d));
175
+ for (const projectSuite of suite.suites) {
176
+ for (const fileSuite of projectSuite.suites) {
177
+ // For every test file...
178
+ const testFile = fileSuite.location.file;
179
+ const deps = new Set();
180
+ // Collect its JS dependencies (helpers).
181
+ for (const file of [testFile, ...((0, _compilationCache.internalDependenciesForTestFile)(testFile) || [])]) {
182
+ // For each helper, get all the imported components.
183
+ for (const componentFile of componentsByImportingFile.get(file) || []) {
184
+ // For each component, get all the dependencies.
185
+ for (const d of depsCollector.get(componentFile) || []) deps.add(d);
186
+ }
187
+ }
188
+ // Now we have test file => all components along with dependencies.
189
+ (0, _compilationCache.setExternalDependencies)(testFile, [...deps]);
179
190
  }
180
- (0, _compilationCache.setExternalDependencies)(filename, [...deps]);
181
191
  }
182
192
  const previewServer = await (0, _vite.preview)(finalConfig);
183
193
  stoppableServer = (0, _utilsBundle2.stoppable)(previewServer.httpServer, 0);
@@ -208,31 +218,16 @@ async function checkSources(buildInfo) {
208
218
  }
209
219
  return false;
210
220
  }
211
- async function checkNewTests(suite, buildInfo, componentRegistry) {
212
- const testFiles = new Set();
213
- for (const project of suite.suites) {
214
- for (const file of project.suites) testFiles.add(file.location.file);
215
- }
216
- let hasNewTests = false;
217
- for (const testFile of testFiles) {
218
- var _buildInfo$tests$test;
219
- const timestamp = (await _fs.default.promises.stat(testFile)).mtimeMs;
220
- if (((_buildInfo$tests$test = buildInfo.tests[testFile]) === null || _buildInfo$tests$test === void 0 ? void 0 : _buildInfo$tests$test.timestamp) !== timestamp) {
221
- const components = await parseTestFile(testFile);
222
- log('changed test:', testFile);
223
- for (const component of components) componentRegistry.set(component.fullName, component);
224
- buildInfo.tests[testFile] = {
225
- timestamp,
226
- components: components.map(c => c.fullName)
227
- };
228
- hasNewTests = true;
229
- }
221
+ async function populateComponentsFromTests(componentRegistry, componentsByImportingFile) {
222
+ const importInfos = await (0, _compilationCache.getUserData)('playwright-ct-core');
223
+ for (const [file, importList] of importInfos) {
224
+ for (const importInfo of importList) componentRegistry.set(importInfo.id, importInfo);
225
+ componentsByImportingFile.set(file, importList.filter(i => !i.isModuleOrAlias).map(i => i.importPath));
230
226
  }
231
- return hasNewTests;
232
227
  }
233
228
  async function checkNewComponents(buildInfo, componentRegistry) {
234
229
  const newComponents = [...componentRegistry.keys()];
235
- const oldComponents = new Map(buildInfo.components.map(c => [c.fullName, c]));
230
+ const oldComponents = new Map(buildInfo.components.map(c => [c.id, c]));
236
231
  let hasNewComponents = false;
237
232
  for (const c of newComponents) {
238
233
  if (!oldComponents.has(c)) {
@@ -240,37 +235,10 @@ async function checkNewComponents(buildInfo, componentRegistry) {
240
235
  break;
241
236
  }
242
237
  }
243
- for (const c of oldComponents.values()) componentRegistry.set(c.fullName, c);
238
+ for (const c of oldComponents.values()) componentRegistry.set(c.id, c);
244
239
  return hasNewComponents;
245
240
  }
246
- async function parseTestFile(testFile) {
247
- const text = await _fs.default.promises.readFile(testFile, 'utf-8');
248
- const ast = (0, _babelBundle.parse)(text, {
249
- errorRecovery: true,
250
- plugins: ['typescript', 'jsx'],
251
- sourceType: 'module'
252
- });
253
- const componentUsages = (0, _tsxTransform.collectComponentUsages)(ast);
254
- const componentNames = componentUsages.names;
255
- const result = [];
256
- (0, _babelBundle.traverse)(ast, {
257
- enter: p => {
258
- if (_babelBundle.types.isImportDeclaration(p.node)) {
259
- const importNode = p.node;
260
- if (!_babelBundle.types.isStringLiteral(importNode.source)) return;
261
- for (const specifier of importNode.specifiers) {
262
- const specifierName = specifier.local.name;
263
- const componentName = componentNames.has(specifierName) ? specifierName : [...componentNames].find(c => c.startsWith(specifierName + '.'));
264
- if (!componentName) continue;
265
- if (_babelBundle.types.isImportNamespaceSpecifier(specifier)) continue;
266
- result.push((0, _tsxTransform.componentInfo)(specifier, importNode.source.value, testFile, componentName));
267
- }
268
- }
269
- }
270
- });
271
- return result;
272
- }
273
- function vitePlugin(registerSource, templateDir, buildInfo, componentRegistry) {
241
+ function vitePlugin(registerSource, templateDir, buildInfo, importInfos, depsCollector) {
274
242
  buildInfo.sources = {};
275
243
  let moduleResolver;
276
244
  return {
@@ -311,11 +279,11 @@ function vitePlugin(registerSource, templateDir, buildInfo, componentRegistry) {
311
279
  const folder = _path.default.dirname(id);
312
280
  const lines = [content, ''];
313
281
  lines.push(registerSource);
314
- for (const [alias, value] of componentRegistry) {
282
+ for (const value of importInfos.values()) {
315
283
  const importPath = value.isModuleOrAlias ? value.importPath : './' + _path.default.relative(folder, value.importPath).replace(/\\/g, '/');
316
- if (value.importedName) lines.push(`const ${alias} = () => import('${importPath}').then((mod) => mod.${value.importedName + (value.importedNameProperty || '')});`);else lines.push(`const ${alias} = () => import('${importPath}').then((mod) => mod.default${value.importedNameProperty || ''});`);
284
+ lines.push(`const ${value.id} = () => import('${importPath}').then((mod) => mod.${value.remoteName || 'default'});`);
317
285
  }
318
- lines.push(`pwRegister({ ${[...componentRegistry.keys()].join(',\n ')} });`);
286
+ lines.push(`__pwRegistry.initialize({ ${[...importInfos.keys()].join(',\n ')} });`);
319
287
  return {
320
288
  code: lines.join('\n'),
321
289
  map: {
@@ -324,12 +292,12 @@ function vitePlugin(registerSource, templateDir, buildInfo, componentRegistry) {
324
292
  };
325
293
  },
326
294
  async writeBundle() {
327
- for (const component of componentRegistry.values()) {
328
- const id = await moduleResolver(component.importPath);
329
- if (!id) continue;
295
+ for (const importInfo of importInfos.values()) {
330
296
  const deps = new Set();
297
+ const id = await moduleResolver(importInfo.importPath);
298
+ if (!id) continue;
331
299
  collectViteModuleDependencies(this, id, deps);
332
- component.deps = [...deps];
300
+ depsCollector.set(importInfo.importPath, [...deps]);
333
301
  }
334
302
  }
335
303
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwright/experimental-ct-core",
3
- "version": "1.41.0",
3
+ "version": "1.41.1",
4
4
  "description": "Playwright Component Testing Helpers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,9 +27,9 @@
27
27
  }
28
28
  },
29
29
  "dependencies": {
30
- "playwright-core": "1.41.0",
30
+ "playwright-core": "1.41.1",
31
31
  "vite": "^4.4.12",
32
- "playwright": "1.41.0"
32
+ "playwright": "1.41.1"
33
33
  },
34
34
  "bin": {
35
35
  "playwright": "cli.js"
@@ -14,33 +14,32 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import type { ImportRegistry } from '../src/injected/importRegistry';
18
+
17
19
  type JsonPrimitive = string | number | boolean | null;
18
20
  type JsonValue = JsonPrimitive | JsonObject | JsonArray;
19
21
  type JsonArray = JsonValue[];
20
22
  export type JsonObject = { [Key in string]?: JsonValue };
21
23
 
22
- // JsxComponentChild can be anything, consider cases like: <>{1}</>, <>{null}</>
23
- export type JsxComponentChild = JsxComponent | string | number | boolean | null;
24
24
  export type JsxComponent = {
25
- __pw_component_marker: true,
26
- kind: 'jsx',
27
- type: string,
25
+ __pw_type: 'jsx',
26
+ type: any,
28
27
  props: Record<string, any>,
29
- children?: JsxComponentChild[],
30
28
  };
31
29
 
32
30
  export type MountOptions = {
33
- props?: Record<string, any>,
34
- slots?: Record<string, string | string[]>,
35
- on?: Record<string, Function>,
36
31
  hooksConfig?: any,
37
32
  };
38
33
 
39
- export type ObjectComponent = {
40
- __pw_component_marker: true,
41
- kind: 'object',
42
- type: string,
43
- options?: MountOptions
34
+ export type ObjectComponentOptions = {
35
+ props?: Record<string, any>;
36
+ slots?: Record<string, string | string[]>;
37
+ on?: Record<string, Function>;
38
+ };
39
+
40
+ export type ObjectComponent = ObjectComponentOptions & {
41
+ __pw_type: 'object-component',
42
+ type: any,
44
43
  };
45
44
 
46
45
  export type Component = JsxComponent | ObjectComponent;
@@ -56,5 +55,10 @@ declare global {
56
55
  __pw_hooks_after_mount?: (<HooksConfig extends JsonObject = JsonObject>(
57
56
  params: { hooksConfig?: HooksConfig; [key: string]: any }
58
57
  ) => Promise<void>)[];
58
+ __pwRegistry: ImportRegistry;
59
+ // Can't start with __pw due to core reuse bindings logic for __pw*.
60
+ __ctDispatchFunction: (ordinal: number, args: any[]) => void;
61
+ __pwUnwrapObject: (value: any) => Promise<any>;
62
+ __pwTransformObject: (value: any, mapping: (v: any) => { result: any } | undefined) => any;
59
63
  }
60
64
  }