mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(ct): before mount hook wrapper (#18616)
This commit is contained in:
parent
c1b9a56079
commit
ba393f51a8
2
packages/playwright-ct-react/hooks.d.ts
vendored
2
packages/playwright-ct-react/hooks.d.ts
vendored
@ -19,7 +19,7 @@ type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
||||
type JsonArray = JsonValue[];
|
||||
type JsonObject = { [Key in string]?: JsonValue };
|
||||
export declare function beforeMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig: HooksConfig }) => Promise<void>
|
||||
callback: (params: { hooksConfig: HooksConfig; App: () => JSX.Element }) => Promise<void | JSX.Element>
|
||||
): void;
|
||||
export declare function afterMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig: HooksConfig }) => Promise<void>
|
||||
|
@ -36,6 +36,7 @@ export function register(components) {
|
||||
|
||||
/**
|
||||
* @param {Component} component
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function render(component) {
|
||||
let componentFunc = registry.get(component.type);
|
||||
@ -69,10 +70,14 @@ function render(component) {
|
||||
}
|
||||
|
||||
window.playwrightMount = async (component, rootElement, hooksConfig) => {
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || [])
|
||||
await hook({ hooksConfig });
|
||||
let App = () => render(component);
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) {
|
||||
const wrapper = await hook({ App, hooksConfig });
|
||||
if (wrapper)
|
||||
App = () => wrapper;
|
||||
}
|
||||
|
||||
ReactDOM.render(render(component), rootElement);
|
||||
ReactDOM.render(App(), rootElement);
|
||||
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
|
||||
await hook({ hooksConfig });
|
||||
|
4
packages/playwright-ct-solid/hooks.d.ts
vendored
4
packages/playwright-ct-solid/hooks.d.ts
vendored
@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JSXElement } from "solid-js";
|
||||
|
||||
type JsonPrimitive = string | number | boolean | null;
|
||||
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
||||
type JsonArray = JsonValue[];
|
||||
type JsonObject = { [Key in string]?: JsonValue };
|
||||
export declare function beforeMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig: HooksConfig }) => Promise<void>
|
||||
callback: (params: { hooksConfig: HooksConfig, App: () => JSXElement }) => Promise<void | JSXElement>
|
||||
): void;
|
||||
export declare function afterMount<HooksConfig extends JsonObject>(
|
||||
callback: (params: { hooksConfig: HooksConfig }) => Promise<void>
|
||||
|
@ -78,10 +78,14 @@ function createComponent(component) {
|
||||
const unmountKey = Symbol('unmountKey');
|
||||
|
||||
window.playwrightMount = async (component, rootElement, hooksConfig) => {
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || [])
|
||||
await hook({ hooksConfig });
|
||||
let App = () => createComponent(component);
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_before_mount || []) {
|
||||
const wrapper = await hook({ App, hooksConfig });
|
||||
if (wrapper)
|
||||
App = () => wrapper;
|
||||
}
|
||||
|
||||
const unmount = solidRender(() => createComponent(component), rootElement);
|
||||
const unmount = solidRender(App, rootElement);
|
||||
rootElement[unmountKey] = unmount;
|
||||
|
||||
for (const hook of /** @type {any} */(window).__pw_hooks_after_mount || [])
|
||||
|
@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,13 +1,17 @@
|
||||
//@ts-check
|
||||
import '../src/assets/index.css';
|
||||
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig }) => {
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig, App }) => {
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}`);
|
||||
|
||||
if (hooksConfig?.routing)
|
||||
return <BrowserRouter><App /></BrowserRouter>;
|
||||
});
|
||||
|
||||
afterMount<HooksConfig>(async () => {
|
@ -1,5 +1,4 @@
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import Button from './components/Button';
|
||||
import DefaultChildren from './components/DefaultChildren';
|
||||
@ -143,7 +142,9 @@ test('get textContent of the empty fragment', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(<BrowserRouter><App /></BrowserRouter>);
|
||||
const component = await mount<HooksConfig>(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,12 +1,17 @@
|
||||
import '../src/assets/index.css';
|
||||
import { beforeMount, afterMount } from '@playwright/experimental-ct-react/hooks';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig }) => {
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig, App }) => {
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}`);
|
||||
|
||||
if (hooksConfig?.routing)
|
||||
return <BrowserRouter><App /></BrowserRouter>;
|
||||
});
|
||||
|
||||
afterMount<HooksConfig>(async () => {
|
@ -1,6 +1,5 @@
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
const { serverFixtures } = require('../../../../tests/config/serverFixtures');
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import Fetch from './components/Fetch';
|
||||
import DelayedData from './components/DelayedData';
|
||||
@ -151,7 +150,9 @@ test('get textContent of the empty fragment', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(<BrowserRouter><App /></BrowserRouter>);
|
||||
const component = await mount<HooksConfig>(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -10,14 +10,15 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.5.0",
|
||||
"solid-js": "^1.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.0.3",
|
||||
"vite-plugin-solid": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"solid-js": "^1.4.7"
|
||||
},
|
||||
"@standaloneDevDependencies": {
|
||||
"@playwright/experimental-ct-solid": "^1.22.2",
|
||||
"@playwright/test": "^1.22.2"
|
||||
|
@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,12 +1,17 @@
|
||||
import '../src/assets/index.css';
|
||||
import { beforeMount, afterMount } from '@playwright/experimental-ct-solid/hooks';
|
||||
import { Router } from "@solidjs/router";
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig }) => {
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig, App }) => {
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}`);
|
||||
|
||||
if (hooksConfig?.routing)
|
||||
return <Router><App /></Router>;
|
||||
});
|
||||
|
||||
afterMount<HooksConfig>(async () => {
|
@ -1,33 +0,0 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #b318f0;
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
@ -1,27 +1,20 @@
|
||||
import type { Component } from 'solid-js';
|
||||
|
||||
import { Routes, Route, A } from "@solidjs/router"
|
||||
import logo from './assets/logo.svg';
|
||||
import styles from './App.module.css';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
|
||||
const App: Component = () => {
|
||||
return (
|
||||
<div class={styles.App}>
|
||||
<header class={styles.header}>
|
||||
<img src={logo} class={styles.logo} alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
class={styles.link}
|
||||
href="https://github.com/solidjs/solid"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn Solid
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
export default function App() {
|
||||
return <>
|
||||
<header>
|
||||
<img src={logo} alt="logo" width={125} height={125} />
|
||||
<A href="/">Login</A>
|
||||
<A href="/dashboard">Dashboard</A>
|
||||
</header>
|
||||
<Routes>
|
||||
<Route path="/">
|
||||
<Route path="/" component={LoginPage} />
|
||||
<Route path="dashboard" component={DashboardPage} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</>
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* @refresh reload */
|
||||
import { render } from 'solid-js/web';
|
||||
import { Router } from "@solidjs/router";
|
||||
import App from './App';
|
||||
import './assets/index.css';
|
||||
|
||||
render(() => <App />, document.getElementById('root') as HTMLElement);
|
||||
render(() => <Router><App /></Router>, document.getElementById('root')!);
|
||||
|
3
tests/components/ct-solid/src/pages/DashboardPage.tsx
Normal file
3
tests/components/ct-solid/src/pages/DashboardPage.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function DashboardPage() {
|
||||
return <main>Dashboard</main>
|
||||
}
|
3
tests/components/ct-solid/src/pages/LoginPage.tsx
Normal file
3
tests/components/ct-solid/src/pages/LoginPage.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function LoginPage() {
|
||||
return <main>Login</main>
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { test, expect } from '@playwright/experimental-ct-solid';
|
||||
import App from './App';
|
||||
import Button from './components/Button';
|
||||
import Counter from './components/Counter';
|
||||
import DefaultChildren from './components/DefaultChildren';
|
||||
@ -81,19 +82,15 @@ test('execute callback when the button is clicked', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('render a default child', async ({ mount }) => {
|
||||
const component = await mount(
|
||||
<DefaultChildren>Main Content</DefaultChildren>
|
||||
);
|
||||
const component = await mount(<DefaultChildren>Main Content</DefaultChildren>);
|
||||
await expect(component).toContainText('Main Content');
|
||||
});
|
||||
|
||||
test('render multiple children', async ({ mount }) => {
|
||||
const component = await mount(
|
||||
<DefaultChildren>
|
||||
<div id="one">One</div>
|
||||
<div id="two">Two</div>
|
||||
</DefaultChildren>
|
||||
);
|
||||
const component = await mount(<DefaultChildren>
|
||||
<div id="one">One</div>
|
||||
<div id="two">Two</div>
|
||||
</DefaultChildren>);
|
||||
await expect(component.locator('#one')).toContainText('One');
|
||||
await expect(component.locator('#two')).toContainText('Two');
|
||||
});
|
||||
@ -107,13 +104,11 @@ test('render a component as slot', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('render named children', async ({ mount }) => {
|
||||
const component = await mount(
|
||||
<MultipleChildren>
|
||||
<div>Header</div>
|
||||
<div>Main Content</div>
|
||||
<div>Footer</div>
|
||||
</MultipleChildren>
|
||||
);
|
||||
const component = await mount(<MultipleChildren>
|
||||
<div>Header</div>
|
||||
<div>Main Content</div>
|
||||
<div>Footer</div>
|
||||
</MultipleChildren>);
|
||||
await expect(component).toContainText('Header');
|
||||
await expect(component).toContainText('Main Content');
|
||||
await expect(component).toContainText('Footer');
|
||||
@ -121,11 +116,9 @@ test('render named children', async ({ mount }) => {
|
||||
|
||||
test('execute callback when a child node is clicked', async ({ mount }) => {
|
||||
let clickFired = false;
|
||||
const component = await mount(
|
||||
<DefaultChildren>
|
||||
<span onClick={() => (clickFired = true)}>Main Content</span>
|
||||
</DefaultChildren>
|
||||
);
|
||||
const component = await mount(<DefaultChildren>
|
||||
<span onClick={() => (clickFired = true)}>Main Content</span>
|
||||
</DefaultChildren>);
|
||||
await component.locator('text=Main Content').click();
|
||||
expect(clickFired).toBeTruthy();
|
||||
});
|
||||
@ -163,3 +156,14 @@ test('get textContent of the empty fragment', async ({ mount }) => {
|
||||
expect(await component.textContent()).toBe('');
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount<HooksConfig>(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
await expect(component.getByRole('main')).toHaveText('Dashboard');
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
});
|
||||
|
@ -3,11 +3,13 @@ import { router } from '../src/router';
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
|
||||
app.use(router);
|
||||
if (hooksConfig?.routing)
|
||||
app.use(router);
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}, app: ${!!app}`);
|
||||
});
|
||||
|
||||
|
@ -122,7 +122,9 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(<App />);
|
||||
const component = await mount<HooksConfig>(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -128,7 +128,9 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(App);
|
||||
const component = await mount<HooksConfig>(App, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -4,11 +4,13 @@ import Button from '../src/components/Button.vue';
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
|
||||
app.use(router as any); // TODO: remove any and fix the various installed conflicting Vue versions
|
||||
if (hooksConfig?.routing)
|
||||
app.use(router as any); // TODO: remove any and fix the various installed conflicting Vue versions
|
||||
app.component('Button', Button);
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}, app: ${!!app}`);
|
||||
});
|
||||
|
@ -143,8 +143,10 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
test('render app and navigate to a page', async ({ page, mount }) => {
|
||||
const component = await mount(App);
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount<HooksConfig>(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -159,8 +159,10 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
test('render app and navigate to a page', async ({ page, mount }) => {
|
||||
const component = await mount(App);
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(App, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -167,8 +167,10 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
await expect(component).toHaveText('');
|
||||
});
|
||||
|
||||
test('render app and navigate to a page', async ({ page, mount }) => {
|
||||
const component = await mount(App);
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount<HooksConfig>(App, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -4,13 +4,17 @@ import { router } from '../src/router';
|
||||
import '../src/assets/index.css';
|
||||
|
||||
export type HooksConfig = {
|
||||
route: string;
|
||||
route?: string;
|
||||
routing?: boolean;
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ Vue, hooksConfig }) => {
|
||||
console.log(`Before mount: ${JSON.stringify(hooksConfig)}`);
|
||||
Vue.use(Router as any); // TODO: remove any and fix the various installed conflicting Vue versions
|
||||
return { router }
|
||||
|
||||
if (hooksConfig?.routing) {
|
||||
Vue.use(Router as any); // TODO: remove any and fix the various installed conflicting Vue versions
|
||||
return { router }
|
||||
}
|
||||
});
|
||||
|
||||
afterMount<HooksConfig>(async ({ instance }) => {
|
||||
|
@ -143,7 +143,9 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(<App />);
|
||||
const component = await mount(<App />, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
@ -152,7 +152,9 @@ test('get textContent of the empty template', async ({ mount }) => {
|
||||
});
|
||||
|
||||
test('navigate to a page by clicking a link', async ({ page, mount }) => {
|
||||
const component = await mount(App);
|
||||
const component = await mount(App, {
|
||||
hooksConfig: { routing: true }
|
||||
});
|
||||
await expect(component.getByRole('main')).toHaveText('Login');
|
||||
await expect(page).toHaveURL('/');
|
||||
await component.getByRole('link', { name: 'Dashboard' }).click();
|
||||
|
Loading…
x
Reference in New Issue
Block a user