This commit is contained in:
Pavel Feldman 2022-03-11 08:00:46 -08:00 коммит произвёл GitHub
Родитель d7c1a57565
Коммит af55f48754
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
34 изменённых файлов: 754 добавлений и 176 удалений

30
package-lock.json сгенерированный
Просмотреть файл

@ -1117,6 +1117,18 @@
"node": ">= 8"
}
},
"node_modules/@playwright/ct-react": {
"resolved": "packages/playwright-ct-react",
"link": true
},
"node_modules/@playwright/ct-svelte": {
"resolved": "packages/playwright-ct-svelte",
"link": true
},
"node_modules/@playwright/ct-vue": {
"resolved": "packages/playwright-ct-vue",
"link": true
},
"node_modules/@playwright/test": {
"resolved": "packages/playwright-test",
"link": true
@ -7364,6 +7376,15 @@
"node": ">= 12"
}
},
"packages/playwright-ct-react": {
"name": "@playwright/ct-react"
},
"packages/playwright-ct-svelte": {
"name": "@playwright/ct-svelte"
},
"packages/playwright-ct-vue": {
"name": "@playwright/ct-vue"
},
"packages/playwright-firefox": {
"version": "1.21.0-next",
"hasInstallScript": true,
@ -8195,6 +8216,15 @@
"fastq": "^1.6.0"
}
},
"@playwright/ct-react": {
"version": "file:packages/playwright-ct-react"
},
"@playwright/ct-svelte": {
"version": "file:packages/playwright-ct-svelte"
},
"@playwright/ct-vue": {
"version": "file:packages/playwright-ct-vue"
},
"@playwright/test": {
"version": "file:packages/playwright-test",
"requires": {

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

@ -0,0 +1 @@
out-ct/

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

@ -0,0 +1,28 @@
<!--
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.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<meta name='color-scheme' content='dark light'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Playwright CT</title>
</head>
<body>
<div id=root></div>
</body>
</html>

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

@ -14,11 +14,12 @@
* limitations under the License.
*/
import { AutoChip, Chip } from './src/chip';
import { HeaderView } from './src/headerView';
import { TestCaseView } from './src/testCaseView';
import './src/theme.css';
import { registerComponent } from './test/component';
import { AutoChip, Chip } from '../src/chip';
import { HeaderView } from '../src/headerView';
import { TestCaseView } from '../src/testCaseView';
import '../src/theme.css';
import { registerComponent } from '@playwright/ct-react/render';
registerComponent('HeaderView', HeaderView);
registerComponent('Chip', Chip);

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

@ -0,0 +1,62 @@
/*
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.
*/
const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
module.exports = {
mode,
entry: {
index: path.join(__dirname, 'index.ts'),
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
},
devtool: mode === 'production' ? false : 'source-map',
output: {
globalObject: 'self',
filename: '[name].bundle.js',
path: path.resolve(__dirname, '..', 'out-ct')
},
module: {
rules: [
{
test: /\.(j|t)sx?$/,
loader: 'babel-loader',
options: {
presets: [
"@babel/preset-typescript",
"@babel/preset-react"
]
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
]
},
plugins: [
new HtmlWebPackPlugin({
title: 'Playwright CT',
template: path.join(__dirname, 'index.html'),
inject: true,
}),
]
};

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

@ -15,10 +15,11 @@
*/
import { PlaywrightTestConfig, devices } from '@playwright/test';
import path from 'path';
import url from 'url';
const config: PlaywrightTestConfig = {
testDir: 'src',
snapshotDir: 'snapshots',
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [
@ -27,6 +28,7 @@ const config: PlaywrightTestConfig = {
['html', { open: 'on-failure' }]
],
use: {
baseURL: url.pathToFileURL(path.join(__dirname, 'out-ct', 'index.html')).toString(),
trace: 'on-first-retry',
},
projects: [

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

@ -15,10 +15,9 @@
*/
import React from 'react';
import { expect, test } from '../test/componentTest';
import { expect, test } from '@playwright/ct-react/test';
import { AutoChip, Chip } from './chip';
test.use({ webpack: require.resolve('../webpack.config.js') });
test.use({ viewport: { width: 500, height: 500 } });
test('expand collapse', async ({ mount }) => {

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

@ -15,10 +15,9 @@
*/
import React from 'react';
import { test, expect } from '../test/componentTest';
import { test, expect } from '@playwright/ct-react/test';
import { HeaderView } from './headerView';
test.use({ webpack: require.resolve('../webpack.config.js') });
test.use({ viewport: { width: 720, height: 200 } });
test('should render counters', async ({ mount }) => {

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

@ -15,11 +15,10 @@
*/
import React from 'react';
import { test, expect } from '../test/componentTest';
import { test, expect } from '@playwright/ct-react/test';
import { TestCaseView } from './testCaseView';
import type { TestCase, TestResult } from '../../playwright-test/src/reporters/html';
test.use({ webpack: require.resolve('../webpack.config.js') });
test.use({ viewport: { width: 800, height: 600 } });
const result: TestResult = {

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

@ -1,77 +0,0 @@
/**
* 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.
*/
const React = require('react');
const ReactDOM = require('react-dom');
const fillStyle = {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
};
const checkerboardCommon = {
...fillStyle,
backgroundSize: '50px 50px',
backgroundPosition: '0 0, 25px 25px',
};
const checkerboardLight = {
...checkerboardCommon,
backgroundColor: '#FFF',
backgroundImage: `linear-gradient(45deg, #00000008 25%, transparent 25%, transparent 75%, #00000008 75%, #00000008),
linear-gradient(45deg, #00000008 25%, transparent 25%, transparent 75%, #00000008 75%, #00000008)`
};
const checkerboardDark = {
...checkerboardCommon,
backgroundColor: '#000',
backgroundImage: `linear-gradient(45deg, #FFFFFF12 25%, transparent 25%, transparent 75%, #FFFFFF12 75%, #FFFFFF12),
linear-gradient(45deg, #FFFFFF12 25%, transparent 25%, transparent 75%, #FFFFFF12 75%, #FFFFFF12)`
};
const Component = ({ style, children }) => {
const checkerboard = window.matchMedia('(prefers-color-scheme: dark)').matches ? checkerboardDark : checkerboardLight;
const bgStyle = { ...checkerboard };
const fgStyle = { ...fillStyle, ...style };
return React.createElement(
React.Fragment, null,
React.createElement('div', { style: bgStyle }),
React.createElement('div', { style: fgStyle, id: 'pw-root' }, children));
};
const registry = new Map();
export const registerComponent = (name, componentFunc) => {
registry.set(name, componentFunc);
};
function render(component) {
const componentFunc = registry.get(component.type) || component.type;
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
if (typeof child === 'string')
return child;
return render(child);
}));
}
window.__playwright_render = component => {
ReactDOM.render(
React.createElement(Component, null, render(component)),
document.getElementById('root'));
};

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

@ -1,79 +0,0 @@
/**
* 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.
*/
import * as path from 'path';
import { test as baseTest, Locator } from '@playwright/test';
type Component = {
type: string;
props: Object;
children: Object[];
};
declare global {
interface Window {
__playwright_render: (component: Component) => void;
}
}
type TestFixtures = {
mount: (component: any) => Promise<Locator>;
webpack: string;
};
export const test = baseTest.extend<TestFixtures>({
webpack: '',
mount: async ({ page, webpack }, use) => {
const webpackConfig = require(webpack);
const outputPath = webpackConfig.output.path;
const filename = webpackConfig.output.filename.replace('[name]', 'playwright');
await use(async (component: Component) => {
await page.route('http://component/index.html', route => {
route.fulfill({
body: `<html>
<meta name='color-scheme' content='dark light'>
<style>html, body { padding: 0; margin: 0; background: #aaa; }</style>
<div id='root' style='width: 100%; height: 100%;'></div>
</html>`,
contentType: 'text/html'
});
});
await page.goto('http://component/index.html');
await page.addScriptTag({ path: path.resolve(__dirname, outputPath, filename) });
const props = { ...component.props };
for (const [key, value] of Object.entries(props)) {
if (typeof value === 'function') {
const functionName = '__pw_func_' + key;
await page.exposeFunction(functionName, value);
(props as any)[key] = functionName;
}
}
await page.evaluate(v => {
const props = v.props;
for (const [key, value] of Object.entries(props)) {
if (typeof value === 'string' && (value as string).startsWith('__pw_func_'))
(props as any)[key] = (window as any)[value];
}
window.__playwright_render({ ...v, props });
}, { ...component, props });
return page.locator('#pw-root');
});
},
});
export { expect } from '@playwright/test';

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

@ -25,7 +25,6 @@ module.exports = {
entry: {
zip: require.resolve('@zip.js/zip.js/dist/zip-no-worker-inflate.min.js'),
app: path.join(__dirname, 'src', 'index.tsx'),
playwright: path.join(__dirname, 'playwright.components.tsx'),
},
resolve: {
extensions: ['.ts', '.js', '.tsx', '.jsx']
@ -60,7 +59,6 @@ module.exports = {
title: 'Playwright Test Report',
template: path.join(__dirname, 'src', 'index.html'),
inject: true,
excludeChunks: ['playwright'],
}),
new BundleJsPlugin(),
]

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

@ -0,0 +1,8 @@
{
"name": "@playwright/ct-react",
"private": true,
"exports": {
"./render": "./render.mjs",
"./test": "./test.js"
}
}

17
packages/playwright-ct-react/render.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
/**
* 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.
*/
export const registerComponent: (name: string, componentFunc: any) => void;

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

@ -0,0 +1,38 @@
/**
* 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.
*/
import React from 'react';
import ReactDOM from 'react-dom';
const registry = new Map();
export const registerComponent = (name, componentFunc) => {
registry.set(name, componentFunc);
};
function render(component) {
const componentFunc = registry.get(component.type) || component.type;
return React.createElement(componentFunc, component.props, ...component.children.map(child => {
if (typeof child === 'string')
return child;
return render(child);
}));
}
window.playwrightMount = component => {
ReactDOM.render(render(component), document.getElementById('root'));
return '#root';
};

34
packages/playwright-ct-react/test.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,34 @@
/**
* 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.
*/
import type {
TestType,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
Locator,
} from '@playwright/test';
interface ComponentFixtures {
mount(component: JSX.Element): Promise<Locator>;
}
export const test: TestType<
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
export { expect } from '@playwright/test';

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

@ -0,0 +1,30 @@
/**
* 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.
*/
const { test: baseTest, expect } = require('@playwright/test');
const { mount } = require('@playwright/test/lib/mount');
const test = baseTest.extend({
mount: async ({ page, baseURL }, use) => {
await use(async (component, options) => {
await page.goto(baseURL);
const selector = await mount(page, component, options);
return page.locator(selector);
});
},
});
module.exports = { test, expect };

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

@ -0,0 +1,8 @@
{
"name": "@playwright/ct-svelte",
"private": true,
"exports": {
"./render": "./render.mjs",
"./test": "./test.js"
}
}

17
packages/playwright-ct-svelte/render.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
/**
* 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.
*/
export const registerComponent: (name: string, componentFunc: any) => void;

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

@ -0,0 +1,34 @@
/**
* 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.
*/
const registry = new Map();
export const registerComponent = (name, componentClass) => {
registry.set(name, componentClass);
};
window.playwrightMount = component => {
const componentCtor = registry.get(component.type);
const wrapper = new componentCtor({
target: document.getElementById('app'),
props: component.options.props,
});
for (const [key, listener] of Object.entries(component.options.on || {}))
wrapper.$on(key, event => listener(event.detail));
return '#app';
};

38
packages/playwright-ct-svelte/test.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,38 @@
/**
* 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.
*/
import type {
TestType,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
Locator,
} from '@playwright/test';
interface ComponentFixtures {
mount(component: any, options?: {
props?: { [key: string]: any },
slots?: { [key: string]: any },
on?: { [key: string]: Function },
}): Promise<Locator>;
}
export const test: TestType<
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
export { expect } from '@playwright/test';

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

@ -0,0 +1,30 @@
/**
* 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.
*/
const { test: baseTest, expect } = require('@playwright/test');
const { mount } = require('@playwright/test/lib/mount');
const test = baseTest.extend({
mount: async ({ page, baseURL }, use) => {
await use(async (component, options) => {
await page.goto(baseURL);
const selector = await mount(page, component, options);
return page.locator(selector);
});
},
});
module.exports = { test, expect };

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

@ -0,0 +1,8 @@
{
"name": "@playwright/ct-vue",
"private": true,
"exports": {
"./render": "./render.mjs",
"./test": "./test.js"
}
}

18
packages/playwright-ct-vue/render.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,18 @@
/**
* 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.
*/
export const initVueTest: (vue: any) => void;
export const registerComponent: (name: string, componentFunc: any) => void;

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

@ -0,0 +1,113 @@
/**
* 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.
*/
import { createApp, setDevtoolsHook, h } from 'vue';
const registry = new Map();
let instance = { createApp, setDevtoolsHook, h };
export const initVueTest = vue => {
instance = vue;
};
export const registerComponent = (name, vueComponent) => {
registry.set(name, vueComponent);
};
const allListeners = [];
function render(component) {
if (typeof component === 'string')
return component;
const componentFunc = registry.get(component.type) || 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:'))
listeners[key.substring('v-on:'.length)] = 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 => {
const app = instance.createApp({
render: () => render(component)
});
instance.setDevtoolsHook(createDevTools(), {});
app.mount('#app');
return '#app';
};

39
packages/playwright-ct-vue/test.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
/**
* 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.
*/
import type {
TestType,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
Locator,
} from '@playwright/test';
interface ComponentFixtures {
mount(component: JSX.Element): Promise<Locator>;
mount(component: any, options?: {
props?: { [key: string]: any },
slots?: { [key: string]: any },
on?: { [key: string]: Function },
}): Promise<Locator>;
}
export const test: TestType<
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
export { expect } from '@playwright/test';

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

@ -0,0 +1,30 @@
/**
* 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.
*/
const { test: baseTest, expect } = require('@playwright/test');
const { mount } = require('@playwright/test/lib/mount');
const test = baseTest.extend({
mount: async ({ page, baseURL }, use) => {
await use(async (component, options) => {
await page.goto(baseURL);
const selector = await mount(page, component, options);
return page.locator(selector);
});
},
});
module.exports = { test, expect };

1
packages/playwright-test/index.d.ts поставляемый
Просмотреть файл

@ -17,4 +17,3 @@
export * from 'playwright-core';
export * from './types/test';
export { default } from './types/test';

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

@ -17,6 +17,7 @@
"./package.json": "./package.json",
"./lib/cli": "./lib/cli.js",
"./lib/experimentalLoader": "./lib/experimentalLoader.js",
"./lib/mount": "./lib/mount.js",
"./reporter": "./reporter.js"
},
"bin": {

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

@ -0,0 +1,68 @@
/**
* 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.
*/
import { Page } from '@playwright/test';
import { createGuid } from 'playwright-core/lib/utils/utils';
export async function mount(page: Page, jsxOrType: any, options: any): Promise<string> {
let component;
if (typeof jsxOrType === 'string')
component = { kind: 'object', type: jsxOrType, options };
else
component = jsxOrType;
const callbacks: Function[] = [];
wrapFunctions(component, page, callbacks);
const dispatchMethod = `__pw_dispatch_${createGuid}`;
await page.exposeFunction(dispatchMethod, (ordinal: number, args: any[]) => {
callbacks[ordinal](...args);
});
const selector = await page.evaluate(async ({ component, dispatchMethod }) => {
const unwrapFunctions = (object: any) => {
for (const [key, value] of Object.entries(object)) {
if (typeof value === 'string' && (value as string).startsWith('__pw_func_')) {
const ordinal = +value.substring('__pw_func_'.length);
object[key] = (...args: any[]) => {
(window as any)[dispatchMethod](ordinal, args);
};
} else if (typeof value === 'object' && value) {
unwrapFunctions(value);
}
}
};
unwrapFunctions(component);
return await (window as any).playwrightMount(component);
}, { component, dispatchMethod });
return selector;
}
function wrapFunctions(object: any, page: Page, callbacks: Function[]) {
for (const [key, value] of Object.entries(object)) {
const type = typeof value;
if (type === 'function') {
const functionName = '__pw_func_' + callbacks.length;
callbacks.push(value as Function);
object[key] = functionName;
} else if (type === 'object' && value) {
wrapFunctions(value, page, callbacks);
}
}
}

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

@ -25,7 +25,7 @@ import type { Location } from './types';
import { tsConfigLoader, TsConfigLoaderResult } from './third_party/tsconfig-loader';
import Module from 'module';
const version = 7;
const version = 8;
const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache');
const sourceMaps: Map<string, string> = new Map();

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

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { types as t } from '@babel/core';
import { types as t, NodePath } from '@babel/core';
import { declare } from '@babel/helper-plugin-utils';
export default declare(api => {
@ -23,6 +23,44 @@ export default declare(api => {
return {
name: 'playwright-debug-transform',
visitor: {
Program(path) {
path.setData('pw-components', new Map());
},
ImportDeclaration(path) {
// Non-JSX transform, replace
// import Button from './ButtonVue.vue'
// import { Card as MyCard } from './Card.vue'
// with
// const Button 'Button', MyCard = 'Card';
const importNode = path.node;
if (!t.isStringLiteral(importNode.source)) {
flushConst(path, true);
return;
}
if (!importNode.source.value.endsWith('.vue') && !importNode.source.value.endsWith('.svelte')) {
flushConst(path, true);
return;
}
const components = path.parentPath.getData('pw-components');
for (const specifier of importNode.specifiers) {
if (t.isImportDefaultSpecifier(specifier)) {
components.set(specifier.local.name, specifier.local.name);
continue;
}
if (t.isImportSpecifier(specifier)) {
if (t.isIdentifier(specifier.imported))
components.set(specifier.local.name, specifier.imported.name);
else
components.set(specifier.local.name, specifier.imported.value);
}
}
flushConst(path, false);
},
JSXElement(path) {
const jsxElement = path.node;
const jsxName = jsxElement.openingElement.name;
@ -34,9 +72,17 @@ export default declare(api => {
for (const jsxAttribute of jsxElement.openingElement.attributes) {
if (t.isJSXAttribute(jsxAttribute)) {
if (!t.isJSXIdentifier(jsxAttribute.name))
let namespace: t.JSXIdentifier | undefined;
let name: t.JSXIdentifier | undefined;
if (t.isJSXNamespacedName(jsxAttribute.name)) {
namespace = jsxAttribute.name.namespace;
name = jsxAttribute.name.name;
} else if (t.isJSXIdentifier(jsxAttribute.name)) {
name = jsxAttribute.name;
}
if (!name)
continue;
const attrName = jsxAttribute.name.name;
const attrName = (namespace ? namespace.name + ':' : '') + name.name;
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))
@ -58,10 +104,10 @@ export default declare(api => {
children.push(child.expression);
else if (t.isJSXSpreadChild(child))
children.push(t.spreadElement(child.expression));
}
path.replaceWith(t.objectExpression([
t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')),
t.objectProperty(t.identifier('type'), t.stringLiteral(name)),
t.objectProperty(t.identifier('props'), t.objectExpression(props)),
t.objectProperty(t.identifier('children'), t.arrayExpression(children)),
@ -70,3 +116,26 @@ export default declare(api => {
}
};
});
function flushConst(importPath: NodePath<t.ImportDeclaration>, keepPath: boolean) {
const importNode = importPath.node;
const importNodes = (importPath.parentPath.node as t.Program).body.filter(i => t.isImportDeclaration(i));
const isLast = importNodes.indexOf(importNode) === importNodes.length - 1;
if (!isLast) {
if (!keepPath)
importPath.remove();
return;
}
const components = importPath.parentPath.getData('pw-components');
if (!components.size)
return;
const variables = [];
for (const [key, value] of components)
variables.push(t.variableDeclarator(t.identifier(key), t.stringLiteral(value)));
importPath.skip();
if (keepPath)
importPath.replaceWithMultiple([importNode, t.variableDeclaration('const', variables)]);
else
importPath.replaceWith(t.variableDeclaration('const', variables));
}

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

@ -176,6 +176,7 @@ const webPackFiles = [
'packages/playwright-core/src/web/traceViewer/webpack-sw.config.js',
'packages/playwright-core/src/web/recorder/webpack.config.js',
'packages/html-reporter/webpack.config.js',
'packages/html-reporter/playwright-ct/webpack.config.js',
];
for (const file of webPackFiles) {
steps.push({

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

@ -171,6 +171,21 @@ const workspace = new Workspace(ROOT_PATH, [
path: path.join(ROOT_PATH, 'packages', 'html-reporter'),
files: [],
}),
new PWPackage({
name: '@playwright/ct-react',
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'),
files: [],
}),
new PWPackage({
name: '@playwright/ct-svelte',
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-svelte'),
files: [],
}),
new PWPackage({
name: '@playwright/ct-vue',
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue'),
files: [],
}),
]);
if (require.main === module) {