@jscutlery/playwright-ct-angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/index.cjs +119 -0
- package/index.d.ts +2 -0
- package/index.js +69 -0
- package/lib/playwright-ct-angular.d.ts +982 -0
- package/package.json +27 -0
- package/register-source.mjs +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 jscutlery
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Playwright Component Testing for Angular _(experimental)_
|
|
2
|
+
|
|
3
|
+
This library brings **Angular support** to [Playwright's **experimental** Component Testing](https://playwright.dev/docs/test-components).
|
|
4
|
+
|
|
5
|
+
This will allow us to test our Angular components with Playwright without building the whole app and with more control.
|
|
6
|
+
|
|
7
|
+
`@jscutlery/playwright-ct-angular` currently supports:
|
|
8
|
+
|
|
9
|
+
- ✅ **Testing [Versatile Angular Components](https://marmicode.io/blog/versatile-angular)**
|
|
10
|
+
- 🎛 **Passing type-safe inputs to the tested components**
|
|
11
|
+
- 🎭 **Spying on component outputs in a type-safe fashion**
|
|
12
|
+
|
|
13
|
+
# 🚀 Writing our first
|
|
14
|
+
|
|
15
|
+
First, we will have to set up Playwright Component Testing as [mentioned below](#setup).
|
|
16
|
+
|
|
17
|
+
⚠️ Make sure to check the [known limitations](#-known-limitations) before writing more tests.
|
|
18
|
+
|
|
19
|
+
## ✅ Basic Test
|
|
20
|
+
|
|
21
|
+
Then, we can write our first test in `.../src/greetings.component.pw.ts`:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { expect, test } from '@jscutlery/playwright-ct-angular';
|
|
25
|
+
import { GreetingsComponent } from './greetings.component';
|
|
26
|
+
|
|
27
|
+
test('<jc-greetings> should be polite', async ({ mount }) => {
|
|
28
|
+
const locator = await mount(GreetingsComponent);
|
|
29
|
+
expect(locator).toHaveText('👋 Hello!');
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🎛 Testing Inputs
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { expect, test } from '@jscutlery/playwright-ct-angular';
|
|
37
|
+
import { GreetingsComponent } from './greetings.component';
|
|
38
|
+
|
|
39
|
+
test('<jc-greetings> should be polite', async ({ mount }) => {
|
|
40
|
+
const locator = await mount(GreetingsComponent, {inputs: {name: 'Edouard'}});
|
|
41
|
+
expect(locator).toHaveText('👋 Hello Edouard!');
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🎭 Spying on outputs
|
|
46
|
+
|
|
47
|
+
`mount()` will automatically create a `jest-mock`'s spy, subscribe to the outputs given through the `spyOutputs`
|
|
48
|
+
option and return them in the `spies` property, in a type-safe way showing only the outputs that are spied on.
|
|
49
|
+
|
|
50
|
+
Note that the `spyOutputs` is type-safe and will only allow properties that exist on the component.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { expect, test } from '@jscutlery/playwright-ct-angular';
|
|
54
|
+
import { NameEditorComponent } from './name-editor.component';
|
|
55
|
+
|
|
56
|
+
test('<jc-name-editor> should be polite', async ({ mount }) => {
|
|
57
|
+
const locator = await mount(NameEditorComponent, {spyOutputs: ['nameChange']});
|
|
58
|
+
|
|
59
|
+
await locator.getByLabel('Name').type('Edouard');
|
|
60
|
+
|
|
61
|
+
expect(locator.spies.nameChange).lastCalledWith('Edouard');
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Passing output callbacks
|
|
66
|
+
|
|
67
|
+
We can also pass custom output callback functions for some extreme cases or if we want to use a custom spy implementation for example or just debug.
|
|
68
|
+
```ts
|
|
69
|
+
await mount(NameEditorComponent, {
|
|
70
|
+
outputs: {
|
|
71
|
+
nameChange(name) {
|
|
72
|
+
console.log(name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 🥸 Providing Test Doubles & Importing Additional Modules
|
|
79
|
+
|
|
80
|
+
Due to the [limitations described below](#-known-limitations), the recommended approach for providing test doubles
|
|
81
|
+
or importing additional modules is to create a test container component in another file.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// recipe-search.component.pw.ts
|
|
85
|
+
import { defer } from 'rxjs';
|
|
86
|
+
import { RecipeSearchTestContainer } from './recipe-search.test-container';
|
|
87
|
+
|
|
88
|
+
test('...', async ({ mount }) => {
|
|
89
|
+
await mount(RecipeSearchTestContainer, {
|
|
90
|
+
inputs: {
|
|
91
|
+
recipes: [
|
|
92
|
+
beer,
|
|
93
|
+
burger
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// recipe-search.test-container.ts
|
|
100
|
+
@Component({
|
|
101
|
+
standalone: true,
|
|
102
|
+
imports: [RecipeSearchComponent],
|
|
103
|
+
template: '<jc-recipe-search></jc-recipe-search>',
|
|
104
|
+
providers: [
|
|
105
|
+
RecipeRepositoryFake,
|
|
106
|
+
{
|
|
107
|
+
provide: RecipeRepository,
|
|
108
|
+
useExisting: RecipeRepositoryFake,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
})
|
|
112
|
+
export class RecipeSearchTestContainer {
|
|
113
|
+
private _repo = inject(RecipeRepositoryFake);
|
|
114
|
+
|
|
115
|
+
@Input() set recipes(recipes: Recipe[]) {
|
|
116
|
+
this._repo.recipes = recipes;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Cf. https://github.com/jscutlery/devkit/tree/main/tests/playwright-ct-angular-wide/src/testing/recipe-repository.fake.ts
|
|
121
|
+
* for a better example. */
|
|
122
|
+
class RecipeRepositoryFake implements RecipeRepositoryDef {
|
|
123
|
+
recipes: Recipe[] = [];
|
|
124
|
+
|
|
125
|
+
searchRecipes() {
|
|
126
|
+
return defer(() => of(this.recipes));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 🎨 Using Styles
|
|
132
|
+
|
|
133
|
+
### Shared Styles
|
|
134
|
+
|
|
135
|
+
In order to import styles that are shared between our tests, we can do so by importing them in `playwright/index.ts`.
|
|
136
|
+
We can also customize the shared `playwright/index.html` nearby.
|
|
137
|
+
|
|
138
|
+
### Specific Styles
|
|
139
|
+
|
|
140
|
+
If we want to load some specific styles for a single test, we might prefer using a test container component:
|
|
141
|
+
```ts
|
|
142
|
+
import styles from './some-styles.css';
|
|
143
|
+
@Component({
|
|
144
|
+
template: '<jc-greetings></jc-greetings>',
|
|
145
|
+
encapsulation: ViewEncapsulation.None,
|
|
146
|
+
styles: [styles]
|
|
147
|
+
})
|
|
148
|
+
class GreetingsTestContainer {}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Angular Material & Angular Libraries with styles
|
|
152
|
+
|
|
153
|
+
As mentioned in [Versatile Angular Style Blog Post](https://marmicode.io/blog/versatile-angular),
|
|
154
|
+
Angular Material and other Angular libraries might use a [Conditional "style" Export](https://nodejs.org/api/packages.html#conditional-exports)
|
|
155
|
+
that allows us to import prebuilt styles
|
|
156
|
+
_(Cf. [Angular Package Format](https://angular.io/guide/angular-package-format) [managing assets in a library](https://angular.io/guide/creating-libraries#managing-assets-in-a-library))_.
|
|
157
|
+
|
|
158
|
+
In that case, we can add the following configuration to our `playwright-ct.config.ts`:
|
|
159
|
+
```ts
|
|
160
|
+
const config: PlaywrightTestConfig = {
|
|
161
|
+
// ...
|
|
162
|
+
use: {
|
|
163
|
+
// ...
|
|
164
|
+
ctViteConfig: {
|
|
165
|
+
resolve: {
|
|
166
|
+
/* @angular/material is using "style" as a Custom Conditional export to expose prebuilt styles etc... */
|
|
167
|
+
conditions: ['style']
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## More examples
|
|
175
|
+
Cf. [/tests/playwright-ct-angular-wide/src](https://github.com/jscutlery/devkit/tree/main/tests/playwright-ct-angular-wide/src)
|
|
176
|
+
|
|
177
|
+
# ⚠️ Known Limitations
|
|
178
|
+
|
|
179
|
+
The way Playwright Component Testing works is different from the way things work with Karma, Jest, Vitest, Cypress etc...
|
|
180
|
+
Playwright Component Testing tests run in a Node.js environment while the component is rendered in a browser.
|
|
181
|
+
|
|
182
|
+
This causes a couple of limitations as we can't directly access the `TestBed's` or the component's internals,
|
|
183
|
+
and we can only exchange serializable data with the component.
|
|
184
|
+
|
|
185
|
+
## 🪄 The Magic Behind the Scenes
|
|
186
|
+
|
|
187
|
+
The magical workaround behind the scenes is that at build time:
|
|
188
|
+
1. Playwright analyses all the calls to `mount()`,
|
|
189
|
+
2. it grabs the first parameter (the component class),
|
|
190
|
+
3. replaces the component class with a unique string (constructed from the component class name and es-module),
|
|
191
|
+
4. adds the component's es-module to Vite entrypoints,
|
|
192
|
+
5. and finally creates a map matching each unique string to the right es-module.
|
|
193
|
+
|
|
194
|
+
This way, when calling `mount()`, Playwright with communicate the unique string to the browser who will know which es-module to load.
|
|
195
|
+
|
|
196
|
+
Cf. https://youtu.be/y3YxX4sFJbM
|
|
197
|
+
Cf. https://github.com/microsoft/playwright/blob/cac67fb94f2c8a0ee82878054c39790e660f17ca/packages/playwright-test/src/tsxTransform.ts#L153
|
|
198
|
+
|
|
199
|
+
## It is currently impossible to...
|
|
200
|
+
|
|
201
|
+
### ...hold the component type in a variable
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// 🛑 this won't work
|
|
205
|
+
const cmp = MyComponent;
|
|
206
|
+
await mount(cmp);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### ...use the component type elsewhere in the file.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// 🛑 this won't work
|
|
213
|
+
test(MyComponent.name, async ({ mount }) => {});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### ...declare components in the same file.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// 🛑 this won't work
|
|
220
|
+
@Component({...})
|
|
221
|
+
class GreetingsComponent {}
|
|
222
|
+
|
|
223
|
+
test('<jc-greetings>', async ({ mount }) => {
|
|
224
|
+
await mount(GreetingsComponent);
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### ...pass any symbol other than a component class
|
|
229
|
+
This makes the following impossible:
|
|
230
|
+
- passing providers to the `mount()` function
|
|
231
|
+
- passing modules to the `mount()` function (this is what currently makes the usage of mounting templates impossible)
|
|
232
|
+
- use non-standalone components
|
|
233
|
+
|
|
234
|
+
### ...use non-[Versatile Angular Style](https://marmicode.io/blog/versatile-angular)
|
|
235
|
+
We'll need a vite plugin to support traditional DI, external templates and stylesheets etc...
|
|
236
|
+
|
|
237
|
+
## 🔮 Future workarounds
|
|
238
|
+
|
|
239
|
+
If you really can't make it to [Versatile Angular Style](https://marmicode.io/blog/versatile-angular),
|
|
240
|
+
we can think of a couple of workarounds like using a vite plugin like **Brandon Roberts' [vite-plugin-angular](https://github.com/analogjs/analog/tree/main/packages/vite-plugin-angular)**
|
|
241
|
+
|
|
242
|
+
We could also implement some custom transform that registers the providers and import modules, like Playwright does for the component class.
|
|
243
|
+
|
|
244
|
+
# Setup
|
|
245
|
+
|
|
246
|
+
## 📦 Install
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
# You can run this command in an existing workspace.
|
|
250
|
+
yarn create playwright --ct # or npm init playwright@latest -- --ct
|
|
251
|
+
|
|
252
|
+
# Choose React
|
|
253
|
+
|
|
254
|
+
# ? Which framework do you use? (experimental) …
|
|
255
|
+
# ❯ react
|
|
256
|
+
# vue
|
|
257
|
+
# svelte
|
|
258
|
+
# solid
|
|
259
|
+
|
|
260
|
+
yarn add -D @jscutlery/playwright-ct-angular @playwright/test # or npm install -D @jscutlery/playwright-ct-angular @playwright/test
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## 🛠 Configure
|
|
264
|
+
|
|
265
|
+
Update `playwright-ct-config.ts` and replace:
|
|
266
|
+
```ts
|
|
267
|
+
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-react';
|
|
268
|
+
import { devices } from '@playwright/experimental-ct-react';
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
with
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import type { PlaywrightTestConfig } from '@jscutlery/playwright-ct-angular';
|
|
275
|
+
import { devices } from '@jscutlery/playwright-ct-angular';
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Change tests extension
|
|
279
|
+
|
|
280
|
+
In order to avoid collisions with other tests (e.g. Jest / Vitest),
|
|
281
|
+
We can replace the default matching extension `.spec.ts` with `.pw.ts`:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
const config: PlaywrightTestConfig = {
|
|
285
|
+
testDir: './',
|
|
286
|
+
testMatch: /pw\.ts$/,
|
|
287
|
+
...
|
|
288
|
+
}
|
|
289
|
+
```
|
package/index.cjs
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var playwright = require('@playwright/test');
|
|
6
|
+
var mount = require('@playwright/test/lib/mount');
|
|
7
|
+
var jestMock = require('jest-mock');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
|
|
10
|
+
function _interopNamespace(e) {
|
|
11
|
+
if (e && e.__esModule) return e;
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n["default"] = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var playwright__namespace = /*#__PURE__*/_interopNamespace(playwright);
|
|
29
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
32
|
+
|
|
33
|
+
function _await(value, then, direct) {
|
|
34
|
+
if (direct) {
|
|
35
|
+
return then ? then(value) : value;
|
|
36
|
+
}
|
|
37
|
+
if (!value || !value.then) {
|
|
38
|
+
value = Promise.resolve(value);
|
|
39
|
+
}
|
|
40
|
+
return then ? value.then(then) : value;
|
|
41
|
+
}
|
|
42
|
+
const noop = () => {};
|
|
43
|
+
function _async(f) {
|
|
44
|
+
return function () {
|
|
45
|
+
for (var args = [], i = 0; i < arguments.length; i++) {
|
|
46
|
+
args[i] = arguments[i];
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return Promise.resolve(f.apply(this, args));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return Promise.reject(e);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
playwright__namespace['_addRunnerPlugin'](() => {
|
|
56
|
+
/* Only fetch upon request to avoid resolution in workers. */
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
58
|
+
const {
|
|
59
|
+
createPlugin
|
|
60
|
+
} = require('@playwright/test/lib/plugins/vitePlugin');
|
|
61
|
+
return createPlugin(path__namespace.join(__dirname, 'register-source.mjs'), noop);
|
|
62
|
+
});
|
|
63
|
+
const mountTest = playwright__namespace.test.extend(mount.fixtures);
|
|
64
|
+
const test = mountTest.extend({
|
|
65
|
+
/**
|
|
66
|
+
* Override mount to return output spies and enjoy type inference.
|
|
67
|
+
* This allows this:
|
|
68
|
+
*
|
|
69
|
+
* const { spies } = mount(..., {outputs: {citySelect: jest.fn()}})
|
|
70
|
+
*
|
|
71
|
+
* instead of this:
|
|
72
|
+
*
|
|
73
|
+
* const citySelectSpy: jest.MockedFunction<(city: string) => void> = jest.fn();
|
|
74
|
+
* mount(..., {outputs: {citySelect: citySelectSpy}});
|
|
75
|
+
*
|
|
76
|
+
* @hack we are willingly not using `async mount(...)` syntax here
|
|
77
|
+
* because it would be transpiled to `_async(function mount() { ... }`
|
|
78
|
+
* by Nx's default rollout config.
|
|
79
|
+
* Playwright dynamically parses the given function so converting
|
|
80
|
+
* async / await to a wrapped function would simply break the library causing
|
|
81
|
+
* a `TypeError: mount is not a function` error.
|
|
82
|
+
*/
|
|
83
|
+
mount({
|
|
84
|
+
mount,
|
|
85
|
+
page
|
|
86
|
+
}, use) {
|
|
87
|
+
return use(_async(function (component, options = {}) {
|
|
88
|
+
var _options$spyOutputs$r, _options$spyOutputs;
|
|
89
|
+
/* 1. Create a spy for each output in `spyOutputs`... */
|
|
90
|
+
const spies = (_options$spyOutputs$r = (_options$spyOutputs = options.spyOutputs) == null ? void 0 : _options$spyOutputs.reduce((acc, key) => {
|
|
91
|
+
return Object.assign({}, acc, {
|
|
92
|
+
[key]: jestMock.fn()
|
|
93
|
+
});
|
|
94
|
+
}, {})) != null ? _options$spyOutputs$r : {};
|
|
95
|
+
/* 2. ...then use it as an output... */
|
|
96
|
+
return _await(mount(component, Object.assign({}, options, {
|
|
97
|
+
outputs: Object.assign({}, options.outputs, spies)
|
|
98
|
+
})), function () {
|
|
99
|
+
/* Grab a locator to the TestBed's root element.
|
|
100
|
+
* We are loading a fresh new page for each test
|
|
101
|
+
* so the id should always be "root0".
|
|
102
|
+
* Otherwise, we'd have to hack our way and somehow grab all
|
|
103
|
+
* the `#root*`, sort them and grab the last one.
|
|
104
|
+
* As a matter of fact, we can't grab a reference to the fixture. */
|
|
105
|
+
const locator = page.locator('#root0');
|
|
106
|
+
/* Add spies to the returned locator. */
|
|
107
|
+
return Object.assign(locator, {
|
|
108
|
+
spies
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const expect = playwright__namespace.expect;
|
|
115
|
+
const devices = playwright__namespace.devices;
|
|
116
|
+
|
|
117
|
+
exports.devices = devices;
|
|
118
|
+
exports.expect = expect;
|
|
119
|
+
exports.test = test;
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as playwright from '@playwright/test';
|
|
2
|
+
import { fixtures } from '@playwright/test/lib/mount';
|
|
3
|
+
import { fn } from 'jest-mock';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
7
|
+
const noop = () => {};
|
|
8
|
+
playwright['_addRunnerPlugin'](() => {
|
|
9
|
+
/* Only fetch upon request to avoid resolution in workers. */
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const {
|
|
12
|
+
createPlugin
|
|
13
|
+
} = require('@playwright/test/lib/plugins/vitePlugin');
|
|
14
|
+
return createPlugin(path.join(__dirname, 'register-source.mjs'), noop);
|
|
15
|
+
});
|
|
16
|
+
const mountTest = playwright.test.extend(fixtures);
|
|
17
|
+
const test = mountTest.extend({
|
|
18
|
+
/**
|
|
19
|
+
* Override mount to return output spies and enjoy type inference.
|
|
20
|
+
* This allows this:
|
|
21
|
+
*
|
|
22
|
+
* const { spies } = mount(..., {outputs: {citySelect: jest.fn()}})
|
|
23
|
+
*
|
|
24
|
+
* instead of this:
|
|
25
|
+
*
|
|
26
|
+
* const citySelectSpy: jest.MockedFunction<(city: string) => void> = jest.fn();
|
|
27
|
+
* mount(..., {outputs: {citySelect: citySelectSpy}});
|
|
28
|
+
*
|
|
29
|
+
* @hack we are willingly not using `async mount(...)` syntax here
|
|
30
|
+
* because it would be transpiled to `_async(function mount() { ... }`
|
|
31
|
+
* by Nx's default rollout config.
|
|
32
|
+
* Playwright dynamically parses the given function so converting
|
|
33
|
+
* async / await to a wrapped function would simply break the library causing
|
|
34
|
+
* a `TypeError: mount is not a function` error.
|
|
35
|
+
*/
|
|
36
|
+
mount({
|
|
37
|
+
mount,
|
|
38
|
+
page
|
|
39
|
+
}, use) {
|
|
40
|
+
return use(async (component, options = {}) => {
|
|
41
|
+
var _options$spyOutputs$r, _options$spyOutputs;
|
|
42
|
+
/* 1. Create a spy for each output in `spyOutputs`... */
|
|
43
|
+
const spies = (_options$spyOutputs$r = (_options$spyOutputs = options.spyOutputs) == null ? void 0 : _options$spyOutputs.reduce((acc, key) => {
|
|
44
|
+
return Object.assign({}, acc, {
|
|
45
|
+
[key]: fn()
|
|
46
|
+
});
|
|
47
|
+
}, {})) != null ? _options$spyOutputs$r : {};
|
|
48
|
+
/* 2. ...then use it as an output... */
|
|
49
|
+
await mount(component, Object.assign({}, options, {
|
|
50
|
+
outputs: Object.assign({}, options.outputs, spies)
|
|
51
|
+
}));
|
|
52
|
+
/* Grab a locator to the TestBed's root element.
|
|
53
|
+
* We are loading a fresh new page for each test
|
|
54
|
+
* so the id should always be "root0".
|
|
55
|
+
* Otherwise, we'd have to hack our way and somehow grab all
|
|
56
|
+
* the `#root*`, sort them and grab the last one.
|
|
57
|
+
* As a matter of fact, we can't grab a reference to the fixture. */
|
|
58
|
+
const locator = page.locator('#root0');
|
|
59
|
+
/* Add spies to the returned locator. */
|
|
60
|
+
return Object.assign(locator, {
|
|
61
|
+
spies
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const expect = playwright.expect;
|
|
67
|
+
const devices = playwright.devices;
|
|
68
|
+
|
|
69
|
+
export { devices, expect, test };
|