This commit is contained in:
Pavel Feldman 2022-05-03 20:25:50 -08:00 коммит произвёл GitHub
Родитель 4b6f53461d
Коммит 214117c9c5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 344 добавлений и 232 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -19,6 +19,7 @@ nohup.out
.tmp
allure*
playwright-report
test-results
/demo/
/packages/*/LICENSE
/packages/*/NOTICE

1
packages/html-reporter/.gitignore поставляемый
Просмотреть файл

@ -11,6 +11,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
dist-pw
*.local
# Editor directories and files

Просмотреть файл

@ -16,19 +16,23 @@
const { test: baseTest, expect, devices, _addRunnerPlugin } = require('@playwright/test');
const { mount } = require('@playwright/test/lib/mount');
const path = require('path');
_addRunnerPlugin(() => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
return createPlugin(
'@playwright/experimental-ct-react/register',
path.join(__dirname, 'registerSource.mjs'),
() => require('@vitejs/plugin-react')());
});
const test = baseTest.extend({
_workerPage: [async ({ browser }, use) => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
const page = await browser._wrapApiCall(async () => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
return page;
});
await use(page);
}, { scope: 'worker' }],

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "@playwright/experimental-ct-react",
"private": true,
"version": "0.0.5",
"version": "0.0.7",
"description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

Просмотреть файл

@ -14,50 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
const registry = new Map();
import { register } from './registerSource.mjs';
export default 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 > *';
register(components);
};

Просмотреть файл

@ -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 { mount } = require('@playwright/test/lib/mount');
const path = require('path');
_addRunnerPlugin(() => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
return createPlugin(
'@playwright/experimental-ct-svelte/register',
path.join(__dirname, 'registerSource.mjs'),
() => require('@sveltejs/vite-plugin-svelte').svelte());
});
const test = baseTest.extend({
_workerPage: [async ({ browser }, use) => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
const page = await browser._wrapApiCall(async () => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
return page;
});
await use(page);
}, { scope: 'worker' }],

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "@playwright/experimental-ct-svelte",
"private": true,
"version": "0.0.5",
"version": "0.0.7",
"description": "Playwright Component Testing for Svelte",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

Просмотреть файл

@ -5,7 +5,7 @@
* 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
* 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,
@ -14,43 +14,8 @@
* limitations under the License.
*/
const registry = new Map();
import { register } from './registerSource.mjs';
export default (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 > *';
export default components => {
register(components);
};

Просмотреть файл

@ -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 { mount } = require('@playwright/test/lib/mount');
const path = require('path');
_addRunnerPlugin(() => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/test/lib/plugins/vitePlugin');
return createPlugin(
'@playwright/experimental-ct-vue/register',
path.join(__dirname, 'registerSource.mjs'),
() => require('@vitejs/plugin-vue')());
});
const test = baseTest.extend({
_workerPage: [async ({ browser }, use) => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
const page = await browser._wrapApiCall(async () => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
return page;
});
await use(page);
}, { scope: 'worker' }],

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "@playwright/experimental-ct-vue",
"private": true,
"version": "0.0.5",
"version": "0.0.7",
"description": "Playwright Component Testing for Vue",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

Просмотреть файл

@ -14,126 +14,8 @@
* limitations under the License.
*/
import { createApp, setDevtoolsHook, h } from 'vue';
import { register } from './registerSource.mjs';
const registry = new Map();
let instance = { createApp, setDevtoolsHook, h };
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 > *';
export default components => {
register(components);
};

Просмотреть файл

@ -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';
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 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[] = [];
wrapFunctions(component, page, callbacks);
const dispatchMethod = `__pw_dispatch_${createGuid()}`;
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
callbacks[ordinal](...args);

Просмотреть файл

@ -17,17 +17,17 @@
import fs from 'fs';
import type { Suite } from '../../types/testReporter';
import path from 'path';
import type { InlineConfig, Plugin, ViteDevServer } from 'vite';
import type { InlineConfig, Plugin, PreviewServer } from 'vite';
import type { TestRunnerPlugin } from '.';
import { parse, traverse, types as t } from '../babelBundle';
import type { ComponentInfo } from '../tsxTransform';
import { collectComponentUsages, componentInfo } from '../tsxTransform';
import type { FullConfig } from '../types';
let viteDevServer: ViteDevServer;
let previewServer: PreviewServer;
export function createPlugin(
registerFunction: string,
registerSourceFile: string,
frameworkPluginFactory: () => Plugin): TestRunnerPlugin {
let configDir: string;
return {
@ -51,24 +51,45 @@ export function createPlugin(
for (const file of project.suites)
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.server = viteConfig.server || {};
viteConfig.server.port = port;
const { createServer } = require('vite');
viteDevServer = await createServer(viteConfig);
await viteDevServer.listen(port);
viteConfig.define = viteConfig.define || {};
viteConfig.define.__VUE_PROD_DEVTOOLS__ = true;
viteConfig.css = viteConfig.css || {};
viteConfig.css.devSourcemap = true;
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 () => {
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();
function vitePlugin(registerFunction: string, files: string[]): Plugin {
function vitePlugin(registerSource: string, files: string[]): Plugin {
return {
name: 'playwright:component-index',
@ -106,7 +127,7 @@ function vitePlugin(registerFunction: string, files: string[]): Plugin {
const folder = path.dirname(id);
const lines = [content, ''];
lines.push(`import register from '${registerFunction}';`);
lines.push(registerSource);
for (const [alias, value] of imports) {
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');

1
packages/recorder/.gitignore поставляемый
Просмотреть файл

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
dist-pw
*.local
# Editor directories and files

1
packages/trace-viewer/.gitignore поставляемый
Просмотреть файл

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
dist-pw
*.local
# Editor directories and files

1
packages/web/.gitignore поставляемый
Просмотреть файл

@ -1 +1,2 @@
dist
dist-pw

1
tests/components/ct-react-vite/.gitignore поставляемый
Просмотреть файл

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
dist-pw
*.local
# Editor directories and files

1
tests/components/ct-svelte-vite/.gitignore поставляемый
Просмотреть файл

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
dist-pw
*.local
# Editor directories and files

2
tests/components/ct-vue-cli/.gitignore поставляемый
Просмотреть файл

@ -1,7 +1,7 @@
.DS_Store
node_modules
/dist
/dist-pw
# local env files
.env.local

Просмотреть файл

@ -11,5 +11,5 @@ defineProps({
</script>
<template>
<button @click="this.$emit('submit', 'hello')">{{ title }}</button>
<button @click="$emit('submit', 'hello')">{{ title }}</button>
</template>

1
tests/components/ct-vue-vite/.gitignore поставляемый
Просмотреть файл

@ -11,6 +11,7 @@ node_modules
.DS_Store
dist
dist-ssr
dist-pw
coverage
*.local

Просмотреть файл

@ -11,5 +11,5 @@ defineProps({
</script>
<template>
<button @click="this.$emit('submit', 'hello')">{{ title }}</button>
<button @click="$emit('submit', 'hello')">{{ title }}</button>
</template>