clean: speed up ct (#13915)
This commit is contained in:
parent
4b6f53461d
commit
214117c9c5
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -19,6 +19,7 @@ nohup.out
|
||||||
.tmp
|
.tmp
|
||||||
allure*
|
allure*
|
||||||
playwright-report
|
playwright-report
|
||||||
|
test-results
|
||||||
/demo/
|
/demo/
|
||||||
/packages/*/LICENSE
|
/packages/*/LICENSE
|
||||||
/packages/*/NOTICE
|
/packages/*/NOTICE
|
||||||
|
|
|
||||||
1
packages/html-reporter/.gitignore
vendored
1
packages/html-reporter/.gitignore
vendored
|
|
@ -11,6 +11,7 @@ lerna-debug.log*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,23 @@
|
||||||
|
|
||||||
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
||||||
const { mount } = require('@playwright/test/lib/mount');
|
const { mount } = require('@playwright/test/lib/mount');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
_addRunnerPlugin(() => {
|
_addRunnerPlugin(() => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
||||||
return createPlugin(
|
return createPlugin(
|
||||||
'@playwright/experimental-ct-react/register',
|
path.join(__dirname, 'registerSource.mjs'),
|
||||||
() => require('@vitejs/plugin-react')());
|
() => require('@vitejs/plugin-react')());
|
||||||
});
|
});
|
||||||
|
|
||||||
const test = baseTest.extend({
|
const test = baseTest.extend({
|
||||||
_workerPage: [async ({ browser }, use) => {
|
_workerPage: [async ({ browser }, use) => {
|
||||||
|
const page = await browser._wrapApiCall(async () => {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
||||||
|
return page;
|
||||||
|
});
|
||||||
await use(page);
|
await use(page);
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-react",
|
"name": "@playwright/experimental-ct-react",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.7",
|
||||||
"description": "Playwright Component Testing for React",
|
"description": "Playwright Component Testing for React",
|
||||||
"repository": "github:Microsoft/playwright",
|
"repository": "github:Microsoft/playwright",
|
||||||
"homepage": "https://playwright.dev",
|
"homepage": "https://playwright.dev",
|
||||||
|
|
|
||||||
|
|
@ -14,50 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import { register } from './registerSource.mjs';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
const registry = new Map();
|
|
||||||
|
|
||||||
export default components => {
|
export default components => {
|
||||||
for (const [name, value] of Object.entries(components))
|
register(components);
|
||||||
registry.set(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function render(component) {
|
|
||||||
let componentFunc = registry.get(component.type);
|
|
||||||
if (!componentFunc) {
|
|
||||||
// Lookup by shorthand.
|
|
||||||
for (const [name, value] of registry) {
|
|
||||||
if (component.type.endsWith(`_${name}`)) {
|
|
||||||
componentFunc = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
|
|
||||||
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
|
||||||
|
|
||||||
componentFunc = componentFunc || component.type;
|
|
||||||
|
|
||||||
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
|
|
||||||
if (typeof child === 'string')
|
|
||||||
return child;
|
|
||||||
return render(child);
|
|
||||||
}).filter(child => {
|
|
||||||
if (typeof child === 'string')
|
|
||||||
return !!child.trim();
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
window.playwrightMount = component => {
|
|
||||||
if (!document.getElementById('root')) {
|
|
||||||
const rootElement = document.createElement('div');
|
|
||||||
rootElement.id = 'root';
|
|
||||||
document.body.append(rootElement);
|
|
||||||
}
|
|
||||||
ReactDOM.render(render(component), document.getElementById('root'));
|
|
||||||
return '#root > *';
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
65
packages/playwright-ct-react/registerSource.mjs
Normal file
65
packages/playwright-ct-react/registerSource.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is injected into the registry as text, no dependencies are allowed.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
const registry = new Map();
|
||||||
|
|
||||||
|
export function register(components) {
|
||||||
|
for (const [name, value] of Object.entries(components))
|
||||||
|
registry.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(component) {
|
||||||
|
let componentFunc = registry.get(component.type);
|
||||||
|
if (!componentFunc) {
|
||||||
|
// Lookup by shorthand.
|
||||||
|
for (const [name, value] of registry) {
|
||||||
|
if (component.type.endsWith(`_${name}`)) {
|
||||||
|
componentFunc = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
|
||||||
|
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
||||||
|
|
||||||
|
componentFunc = componentFunc || component.type;
|
||||||
|
|
||||||
|
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
|
||||||
|
if (typeof child === 'string')
|
||||||
|
return child;
|
||||||
|
return render(child);
|
||||||
|
}).filter(child => {
|
||||||
|
if (typeof child === 'string')
|
||||||
|
return !!child.trim();
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.playwrightMount = component => {
|
||||||
|
if (!document.getElementById('root')) {
|
||||||
|
const rootElement = document.createElement('div');
|
||||||
|
rootElement.id = 'root';
|
||||||
|
document.body.append(rootElement);
|
||||||
|
}
|
||||||
|
ReactDOM.render(render(component), document.getElementById('root'));
|
||||||
|
return '#root > *';
|
||||||
|
};
|
||||||
|
|
@ -16,19 +16,23 @@
|
||||||
|
|
||||||
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
||||||
const { mount } = require('@playwright/test/lib/mount');
|
const { mount } = require('@playwright/test/lib/mount');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
_addRunnerPlugin(() => {
|
_addRunnerPlugin(() => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
||||||
return createPlugin(
|
return createPlugin(
|
||||||
'@playwright/experimental-ct-svelte/register',
|
path.join(__dirname, 'registerSource.mjs'),
|
||||||
() => require('@sveltejs/vite-plugin-svelte').svelte());
|
() => require('@sveltejs/vite-plugin-svelte').svelte());
|
||||||
});
|
});
|
||||||
|
|
||||||
const test = baseTest.extend({
|
const test = baseTest.extend({
|
||||||
_workerPage: [async ({ browser }, use) => {
|
_workerPage: [async ({ browser }, use) => {
|
||||||
|
const page = await browser._wrapApiCall(async () => {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
||||||
|
return page;
|
||||||
|
});
|
||||||
await use(page);
|
await use(page);
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-svelte",
|
"name": "@playwright/experimental-ct-svelte",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.7",
|
||||||
"description": "Playwright Component Testing for Svelte",
|
"description": "Playwright Component Testing for Svelte",
|
||||||
"repository": "github:Microsoft/playwright",
|
"repository": "github:Microsoft/playwright",
|
||||||
"homepage": "https://playwright.dev",
|
"homepage": "https://playwright.dev",
|
||||||
|
|
|
||||||
|
|
@ -14,43 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const registry = new Map();
|
import { register } from './registerSource.mjs';
|
||||||
|
|
||||||
export default (components, options) => {
|
export default components => {
|
||||||
// SvelteKit won't have window in the scope, so it requires explicit initialization.
|
register(components);
|
||||||
const win = options?.window || window;
|
|
||||||
win.playwrightMount = playwrightMount;
|
|
||||||
|
|
||||||
for (const [name, value] of Object.entries(components))
|
|
||||||
registry.set(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const playwrightMount = component => {
|
|
||||||
if (!document.getElementById('root')) {
|
|
||||||
const rootElement = document.createElement('div');
|
|
||||||
rootElement.id = 'root';
|
|
||||||
document.body.append(rootElement);
|
|
||||||
}
|
|
||||||
let componentCtor = registry.get(component.type);
|
|
||||||
if (!componentCtor) {
|
|
||||||
// Lookup by shorthand.
|
|
||||||
for (const [name, value] of registry) {
|
|
||||||
if (component.type.endsWith(`_${name}_svelte`)) {
|
|
||||||
componentCtor = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!componentCtor)
|
|
||||||
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
|
||||||
|
|
||||||
const wrapper = new componentCtor({
|
|
||||||
target: document.getElementById('root'),
|
|
||||||
props: component.options?.props,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const [key, listener] of Object.entries(component.options?.on || {}))
|
|
||||||
wrapper.$on(key, event => listener(event.detail));
|
|
||||||
return '#root > *';
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
58
packages/playwright-ct-svelte/registerSource.mjs
Normal file
58
packages/playwright-ct-svelte/registerSource.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is injected into the registry as text, no dependencies are allowed.
|
||||||
|
|
||||||
|
const registry = new Map();
|
||||||
|
|
||||||
|
export function register(components, options) {
|
||||||
|
// SvelteKit won't have window in the scope, so it requires explicit initialization.
|
||||||
|
const win = options?.window || window;
|
||||||
|
win.playwrightMount = playwrightMount;
|
||||||
|
|
||||||
|
for (const [name, value] of Object.entries(components))
|
||||||
|
registry.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const playwrightMount = component => {
|
||||||
|
if (!document.getElementById('root')) {
|
||||||
|
const rootElement = document.createElement('div');
|
||||||
|
rootElement.id = 'root';
|
||||||
|
document.body.append(rootElement);
|
||||||
|
}
|
||||||
|
let componentCtor = registry.get(component.type);
|
||||||
|
if (!componentCtor) {
|
||||||
|
// Lookup by shorthand.
|
||||||
|
for (const [name, value] of registry) {
|
||||||
|
if (component.type.endsWith(`_${name}_svelte`)) {
|
||||||
|
componentCtor = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!componentCtor)
|
||||||
|
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
||||||
|
|
||||||
|
const wrapper = new componentCtor({
|
||||||
|
target: document.getElementById('root'),
|
||||||
|
props: component.options?.props,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [key, listener] of Object.entries(component.options?.on || {}))
|
||||||
|
wrapper.$on(key, event => listener(event.detail));
|
||||||
|
return '#root > *';
|
||||||
|
};
|
||||||
|
|
@ -16,19 +16,23 @@
|
||||||
|
|
||||||
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
|
||||||
const { mount } = require('@playwright/test/lib/mount');
|
const { mount } = require('@playwright/test/lib/mount');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
_addRunnerPlugin(() => {
|
_addRunnerPlugin(() => {
|
||||||
// Only fetch upon request to avoid resolution in workers.
|
// Only fetch upon request to avoid resolution in workers.
|
||||||
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
|
||||||
return createPlugin(
|
return createPlugin(
|
||||||
'@playwright/experimental-ct-vue/register',
|
path.join(__dirname, 'registerSource.mjs'),
|
||||||
() => require('@vitejs/plugin-vue')());
|
() => require('@vitejs/plugin-vue')());
|
||||||
});
|
});
|
||||||
|
|
||||||
const test = baseTest.extend({
|
const test = baseTest.extend({
|
||||||
_workerPage: [async ({ browser }, use) => {
|
_workerPage: [async ({ browser }, use) => {
|
||||||
|
const page = await browser._wrapApiCall(async () => {
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
await page.addInitScript('navigator.serviceWorker.register = () => {}');
|
||||||
|
return page;
|
||||||
|
});
|
||||||
await use(page);
|
await use(page);
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-vue",
|
"name": "@playwright/experimental-ct-vue",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.7",
|
||||||
"description": "Playwright Component Testing for Vue",
|
"description": "Playwright Component Testing for Vue",
|
||||||
"repository": "github:Microsoft/playwright",
|
"repository": "github:Microsoft/playwright",
|
||||||
"homepage": "https://playwright.dev",
|
"homepage": "https://playwright.dev",
|
||||||
|
|
|
||||||
|
|
@ -14,126 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createApp, setDevtoolsHook, h } from 'vue';
|
import { register } from './registerSource.mjs';
|
||||||
|
|
||||||
const registry = new Map();
|
export default components => {
|
||||||
let instance = { createApp, setDevtoolsHook, h };
|
register(components);
|
||||||
|
|
||||||
export default (components, options) => {
|
|
||||||
if (options)
|
|
||||||
instance = options;
|
|
||||||
for (const [name, value] of Object.entries(components))
|
|
||||||
registry.set(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const allListeners = [];
|
|
||||||
|
|
||||||
function render(component) {
|
|
||||||
if (typeof component === 'string')
|
|
||||||
return component;
|
|
||||||
|
|
||||||
let componentFunc = registry.get(component.type);
|
|
||||||
if (!componentFunc) {
|
|
||||||
// Lookup by shorthand.
|
|
||||||
for (const [name, value] of registry) {
|
|
||||||
if (component.type.endsWith(`_${name}_vue`)) {
|
|
||||||
componentFunc = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
|
|
||||||
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
|
||||||
|
|
||||||
componentFunc = componentFunc || component.type;
|
|
||||||
|
|
||||||
const isVueComponent = componentFunc !== component.type;
|
|
||||||
|
|
||||||
const children = [];
|
|
||||||
const slots = {};
|
|
||||||
const listeners = {};
|
|
||||||
let props = {};
|
|
||||||
|
|
||||||
if (component.kind === 'jsx') {
|
|
||||||
for (const child of component.children || []) {
|
|
||||||
if (child.type === 'template') {
|
|
||||||
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
|
|
||||||
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
|
|
||||||
slots[slot] = child.children.map(render);
|
|
||||||
} else {
|
|
||||||
children.push(render(child));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(component.props)) {
|
|
||||||
if (key.startsWith('v-on:')) {
|
|
||||||
const event = key.substring('v-on:'.length);
|
|
||||||
if (isVueComponent)
|
|
||||||
listeners[event] = value;
|
|
||||||
else
|
|
||||||
props[`on${event[0].toUpperCase()}${event.substring(1)}`] = value;
|
|
||||||
} else {
|
|
||||||
props[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.kind === 'object') {
|
|
||||||
// Vue test util syntax.
|
|
||||||
for (const [key, value] of Object.entries(component.options.slots || {})) {
|
|
||||||
if (key === 'default')
|
|
||||||
children.push(value);
|
|
||||||
else
|
|
||||||
slots[key] = value;
|
|
||||||
}
|
|
||||||
props = component.options.props || {};
|
|
||||||
for (const [key, value] of Object.entries(component.options.on || {}))
|
|
||||||
listeners[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastArg;
|
|
||||||
if (Object.entries(slots).length) {
|
|
||||||
lastArg = slots;
|
|
||||||
if (children.length)
|
|
||||||
slots.default = children;
|
|
||||||
} else if (children.length) {
|
|
||||||
lastArg = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrapper = instance.h(componentFunc, props, lastArg);
|
|
||||||
allListeners.push([wrapper, listeners]);
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDevTools() {
|
|
||||||
return {
|
|
||||||
emit(eventType, ...payload) {
|
|
||||||
if (eventType === 'component:emit') {
|
|
||||||
const [, componentVM, event, eventArgs] = payload;
|
|
||||||
for (const [wrapper, listeners] of allListeners) {
|
|
||||||
if (wrapper.component !== componentVM)
|
|
||||||
continue;
|
|
||||||
const listener = listeners[event];
|
|
||||||
if (!listener)
|
|
||||||
return;
|
|
||||||
listener(...eventArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
window.playwrightMount = async component => {
|
|
||||||
if (!document.getElementById('root')) {
|
|
||||||
const rootElement = document.createElement('div');
|
|
||||||
rootElement.id = 'root';
|
|
||||||
document.body.append(rootElement);
|
|
||||||
}
|
|
||||||
const app = instance.createApp({
|
|
||||||
render: () => render(component)
|
|
||||||
});
|
|
||||||
instance.setDevtoolsHook(createDevTools(), {});
|
|
||||||
app.mount('#root');
|
|
||||||
return '#root > *';
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
138
packages/playwright-ct-vue/registerSource.mjs
Normal file
138
packages/playwright-ct-vue/registerSource.mjs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is injected into the registry as text, no dependencies are allowed.
|
||||||
|
|
||||||
|
import { createApp, setDevtoolsHook, h } from 'vue';
|
||||||
|
|
||||||
|
const registry = new Map();
|
||||||
|
|
||||||
|
export function register(components) {
|
||||||
|
for (const [name, value] of Object.entries(components))
|
||||||
|
registry.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allListeners = [];
|
||||||
|
|
||||||
|
function render(component) {
|
||||||
|
if (typeof component === 'string')
|
||||||
|
return component;
|
||||||
|
|
||||||
|
let componentFunc = registry.get(component.type);
|
||||||
|
if (!componentFunc) {
|
||||||
|
// Lookup by shorthand.
|
||||||
|
for (const [name, value] of registry) {
|
||||||
|
if (component.type.endsWith(`_${name}_vue`)) {
|
||||||
|
componentFunc = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
|
||||||
|
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...registry.keys()]}`);
|
||||||
|
|
||||||
|
componentFunc = componentFunc || component.type;
|
||||||
|
|
||||||
|
const isVueComponent = componentFunc !== component.type;
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
const slots = {};
|
||||||
|
const listeners = {};
|
||||||
|
let props = {};
|
||||||
|
|
||||||
|
if (component.kind === 'jsx') {
|
||||||
|
for (const child of component.children || []) {
|
||||||
|
if (child.type === 'template') {
|
||||||
|
const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:'));
|
||||||
|
const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default';
|
||||||
|
slots[slot] = child.children.map(render);
|
||||||
|
} else {
|
||||||
|
children.push(render(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(component.props)) {
|
||||||
|
if (key.startsWith('v-on:')) {
|
||||||
|
const event = key.substring('v-on:'.length);
|
||||||
|
if (isVueComponent)
|
||||||
|
listeners[event] = value;
|
||||||
|
else
|
||||||
|
props[`on${event[0].toUpperCase()}${event.substring(1)}`] = value;
|
||||||
|
} else {
|
||||||
|
props[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.kind === 'object') {
|
||||||
|
// Vue test util syntax.
|
||||||
|
for (const [key, value] of Object.entries(component.options.slots || {})) {
|
||||||
|
if (key === 'default')
|
||||||
|
children.push(value);
|
||||||
|
else
|
||||||
|
slots[key] = value;
|
||||||
|
}
|
||||||
|
props = component.options.props || {};
|
||||||
|
for (const [key, value] of Object.entries(component.options.on || {}))
|
||||||
|
listeners[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastArg;
|
||||||
|
if (Object.entries(slots).length) {
|
||||||
|
lastArg = slots;
|
||||||
|
if (children.length)
|
||||||
|
slots.default = children;
|
||||||
|
} else if (children.length) {
|
||||||
|
lastArg = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = h(componentFunc, props, lastArg);
|
||||||
|
allListeners.push([wrapper, listeners]);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDevTools() {
|
||||||
|
return {
|
||||||
|
emit(eventType, ...payload) {
|
||||||
|
if (eventType === 'component:emit') {
|
||||||
|
const [, componentVM, event, eventArgs] = payload;
|
||||||
|
for (const [wrapper, listeners] of allListeners) {
|
||||||
|
if (wrapper.component !== componentVM)
|
||||||
|
continue;
|
||||||
|
const listener = listeners[event];
|
||||||
|
if (!listener)
|
||||||
|
return;
|
||||||
|
listener(...eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.playwrightMount = async component => {
|
||||||
|
if (!document.getElementById('root')) {
|
||||||
|
const rootElement = document.createElement('div');
|
||||||
|
rootElement.id = 'root';
|
||||||
|
document.body.append(rootElement);
|
||||||
|
}
|
||||||
|
const app = createApp({
|
||||||
|
render: () => render(component)
|
||||||
|
});
|
||||||
|
setDevtoolsHook(createDevTools(), {});
|
||||||
|
app.mount('#root');
|
||||||
|
return '#root > *';
|
||||||
|
};
|
||||||
|
|
@ -18,6 +18,12 @@ import type { Page, ViewportSize } from '@playwright/test';
|
||||||
import { createGuid } from 'playwright-core/lib/utils';
|
import { createGuid } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
export async function mount(page: Page, jsxOrType: any, options: any, baseURL: string, viewport: ViewportSize): Promise<string> {
|
export async function mount(page: Page, jsxOrType: any, options: any, baseURL: string, viewport: ViewportSize): Promise<string> {
|
||||||
|
return await (page as any)._wrapApiCall(async () => {
|
||||||
|
return await innerMount(page, jsxOrType, options, baseURL, viewport);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function innerMount(page: Page, jsxOrType: any, options: any, baseURL: string, viewport: ViewportSize): Promise<string> {
|
||||||
await page.goto('about:blank');
|
await page.goto('about:blank');
|
||||||
await (page as any)._resetForReuse();
|
await (page as any)._resetForReuse();
|
||||||
await (page.context() as any)._resetForReuse();
|
await (page.context() as any)._resetForReuse();
|
||||||
|
|
@ -33,7 +39,6 @@ export async function mount(page: Page, jsxOrType: any, options: any, baseURL: s
|
||||||
const callbacks: Function[] = [];
|
const callbacks: Function[] = [];
|
||||||
wrapFunctions(component, page, callbacks);
|
wrapFunctions(component, page, callbacks);
|
||||||
|
|
||||||
|
|
||||||
const dispatchMethod = `__pw_dispatch_${createGuid()}`;
|
const dispatchMethod = `__pw_dispatch_${createGuid()}`;
|
||||||
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
|
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
|
||||||
callbacks[ordinal](...args);
|
callbacks[ordinal](...args);
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,17 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import type { Suite } from '../../types/testReporter';
|
import type { Suite } from '../../types/testReporter';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { InlineConfig, Plugin, ViteDevServer } from 'vite';
|
import type { InlineConfig, Plugin, PreviewServer } from 'vite';
|
||||||
import type { TestRunnerPlugin } from '.';
|
import type { TestRunnerPlugin } from '.';
|
||||||
import { parse, traverse, types as t } from '../babelBundle';
|
import { parse, traverse, types as t } from '../babelBundle';
|
||||||
import type { ComponentInfo } from '../tsxTransform';
|
import type { ComponentInfo } from '../tsxTransform';
|
||||||
import { collectComponentUsages, componentInfo } from '../tsxTransform';
|
import { collectComponentUsages, componentInfo } from '../tsxTransform';
|
||||||
import type { FullConfig } from '../types';
|
import type { FullConfig } from '../types';
|
||||||
|
|
||||||
let viteDevServer: ViteDevServer;
|
let previewServer: PreviewServer;
|
||||||
|
|
||||||
export function createPlugin(
|
export function createPlugin(
|
||||||
registerFunction: string,
|
registerSourceFile: string,
|
||||||
frameworkPluginFactory: () => Plugin): TestRunnerPlugin {
|
frameworkPluginFactory: () => Plugin): TestRunnerPlugin {
|
||||||
let configDir: string;
|
let configDir: string;
|
||||||
return {
|
return {
|
||||||
|
|
@ -51,24 +51,45 @@ export function createPlugin(
|
||||||
for (const file of project.suites)
|
for (const file of project.suites)
|
||||||
files.add(file.location!.file);
|
files.add(file.location!.file);
|
||||||
}
|
}
|
||||||
viteConfig.plugins.push(vitePlugin(registerFunction, [...files]));
|
const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8');
|
||||||
|
viteConfig.plugins.push(vitePlugin(registerSource, [...files]));
|
||||||
viteConfig.configFile = viteConfig.configFile || false;
|
viteConfig.configFile = viteConfig.configFile || false;
|
||||||
viteConfig.server = viteConfig.server || {};
|
viteConfig.define = viteConfig.define || {};
|
||||||
viteConfig.server.port = port;
|
viteConfig.define.__VUE_PROD_DEVTOOLS__ = true;
|
||||||
const { createServer } = require('vite');
|
viteConfig.css = viteConfig.css || {};
|
||||||
viteDevServer = await createServer(viteConfig);
|
viteConfig.css.devSourcemap = true;
|
||||||
await viteDevServer.listen(port);
|
viteConfig.preview = { port };
|
||||||
|
viteConfig.build = {
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
rollupOptions: {
|
||||||
|
treeshake: false,
|
||||||
|
input: {
|
||||||
|
index: path.join(viteConfig.root, 'playwright', 'index.html')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourcemap: true,
|
||||||
|
outDir: viteConfig?.build?.outDir || path.join(viteConfig.root, './dist-pw/')
|
||||||
|
};
|
||||||
|
const { build, preview } = require('vite');
|
||||||
|
await build(viteConfig);
|
||||||
|
previewServer = await preview(viteConfig);
|
||||||
},
|
},
|
||||||
|
|
||||||
teardown: async () => {
|
teardown: async () => {
|
||||||
await viteDevServer.close();
|
await new Promise<void>((f, r) => previewServer.httpServer.close(err => {
|
||||||
|
if (err)
|
||||||
|
r(err);
|
||||||
|
else
|
||||||
|
f();
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const imports: Map<string, ComponentInfo> = new Map();
|
const imports: Map<string, ComponentInfo> = new Map();
|
||||||
|
|
||||||
function vitePlugin(registerFunction: string, files: string[]): Plugin {
|
function vitePlugin(registerSource: string, files: string[]): Plugin {
|
||||||
return {
|
return {
|
||||||
name: 'playwright:component-index',
|
name: 'playwright:component-index',
|
||||||
|
|
||||||
|
|
@ -106,7 +127,7 @@ function vitePlugin(registerFunction: string, files: string[]): Plugin {
|
||||||
|
|
||||||
const folder = path.dirname(id);
|
const folder = path.dirname(id);
|
||||||
const lines = [content, ''];
|
const lines = [content, ''];
|
||||||
lines.push(`import register from '${registerFunction}';`);
|
lines.push(registerSource);
|
||||||
|
|
||||||
for (const [alias, value] of imports) {
|
for (const [alias, value] of imports) {
|
||||||
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');
|
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');
|
||||||
|
|
|
||||||
1
packages/recorder/.gitignore
vendored
1
packages/recorder/.gitignore
vendored
|
|
@ -10,6 +10,7 @@ lerna-debug.log*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|
|
||||||
1
packages/trace-viewer/.gitignore
vendored
1
packages/trace-viewer/.gitignore
vendored
|
|
@ -10,6 +10,7 @@ lerna-debug.log*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|
|
||||||
1
packages/web/.gitignore
vendored
1
packages/web/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
dist
|
dist
|
||||||
|
dist-pw
|
||||||
1
tests/components/ct-react-vite/.gitignore
vendored
1
tests/components/ct-react-vite/.gitignore
vendored
|
|
@ -10,6 +10,7 @@ lerna-debug.log*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|
|
||||||
1
tests/components/ct-svelte-vite/.gitignore
vendored
1
tests/components/ct-svelte-vite/.gitignore
vendored
|
|
@ -10,6 +10,7 @@ lerna-debug.log*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
|
|
|
||||||
2
tests/components/ct-vue-cli/.gitignore
vendored
2
tests/components/ct-vue-cli/.gitignore
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
/dist
|
/dist
|
||||||
|
/dist-pw
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ defineProps({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button @click="this.$emit('submit', 'hello')">{{ title }}</button>
|
<button @click="$emit('submit', 'hello')">{{ title }}</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
1
tests/components/ct-vue-vite/.gitignore
vendored
1
tests/components/ct-vue-vite/.gitignore
vendored
|
|
@ -11,6 +11,7 @@ node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist-pw
|
||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ defineProps({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button @click="this.$emit('submit', 'hello')">{{ title }}</button>
|
<button @click="$emit('submit', 'hello')">{{ title }}</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue