@jscutlery/playwright-ct-angular 0.2.2 → 0.4.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/README.md +37 -36
- package/hooks.esm.d.ts +1 -0
- package/index.cjs.js +39 -84
- package/index.esm.d.ts +1 -0
- package/index.esm.js +31 -82
- package/package.json +7 -1
- package/register-source.mjs +7 -6
- package/register.cjs.js +6 -5
- package/register.esm.d.ts +2 -0
- package/register.esm.js +6 -5
- package/src/index.d.ts +26 -19
- package/src/transform-angular.d.ts +29 -0
- package/transform-angular.cjs.d.ts +2 -0
- package/transform-angular.cjs.default.js +1 -0
- package/transform-angular.cjs.js +224 -0
- package/transform-angular.cjs.mjs +2 -0
- package/transform-angular.esm.d.ts +2 -0
- package/transform-angular.esm.js +218 -0
- package/src/lib/playwright-ct-angular.d.ts +0 -1062
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Playwright Component Testing for Angular _(experimental)_
|
|
2
2
|
|
|
3
|
-
This library brings **Angular support** to [Playwright's **experimental
|
|
3
|
+
This library brings **Angular support** to [Playwright's **experimental
|
|
4
|
+
** Component Testing](https://playwright.dev/docs/test-components).
|
|
4
5
|
|
|
5
6
|
This will allow us to test our Angular components with Playwright without building the whole app and with more control.
|
|
6
7
|
|
|
7
8
|
`@jscutlery/playwright-ct-angular` currently supports:
|
|
8
9
|
|
|
9
10
|
- ✅ **Testing [Versatile Angular Components](https://marmicode.io/blog/versatile-angular)**
|
|
10
|
-
- 🎛 **Passing type-safe inputs to the tested components**
|
|
11
|
+
- 🎛 **Passing type-safe inputs to the tested components**
|
|
11
12
|
- 🎭 **Spying on component outputs in a type-safe fashion**
|
|
12
13
|
|
|
13
14
|
https://user-images.githubusercontent.com/2674658/206226065-ba856329-dda7-43b1-9c28-4416b190f4d4.mp4
|
|
@@ -39,37 +40,19 @@ import { expect, test } from '@jscutlery/playwright-ct-angular';
|
|
|
39
40
|
import { GreetingsComponent } from './greetings.component';
|
|
40
41
|
|
|
41
42
|
test('<jc-greetings> should be polite', async ({ mount }) => {
|
|
42
|
-
const locator = await mount(GreetingsComponent, {
|
|
43
|
+
const locator = await mount(GreetingsComponent, { props: { name: 'Edouard' } });
|
|
43
44
|
expect(locator).toHaveText('👋 Hello Edouard!');
|
|
44
45
|
});
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
## 🎭 Spying on outputs
|
|
48
|
-
|
|
49
|
-
`mount()` will automatically create a `jest-mock`'s spy, subscribe to the outputs given through the `spyOutputs`
|
|
50
|
-
option and return them in the `spies` property, in a type-safe way showing only the outputs that are spied on.
|
|
51
|
-
|
|
52
|
-
Note that the `spyOutputs` is type-safe and will only allow properties that exist on the component.
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { expect, test } from '@jscutlery/playwright-ct-angular';
|
|
56
|
-
import { NameEditorComponent } from './name-editor.component';
|
|
57
|
-
|
|
58
|
-
test('<jc-name-editor> should be polite', async ({ mount }) => {
|
|
59
|
-
const locator = await mount(NameEditorComponent, {spyOutputs: ['nameChange']});
|
|
60
|
-
|
|
61
|
-
await locator.getByLabel('Name').type('Edouard');
|
|
62
|
-
|
|
63
|
-
expect(locator.spies.nameChange).lastCalledWith('Edouard');
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
48
|
### Passing output callbacks
|
|
68
49
|
|
|
69
|
-
We can also pass custom output callback functions for some extreme cases or if we want to use a custom spy
|
|
50
|
+
We can also pass custom output callback functions for some extreme cases or if we want to use a custom spy
|
|
51
|
+
implementation for example or just debug.
|
|
52
|
+
|
|
70
53
|
```ts
|
|
71
54
|
await mount(NameEditorComponent, {
|
|
72
|
-
|
|
55
|
+
on: {
|
|
73
56
|
nameChange(name) {
|
|
74
57
|
console.log(name);
|
|
75
58
|
}
|
|
@@ -89,7 +72,7 @@ import { RecipeSearchTestContainer } from './recipe-search.test-container';
|
|
|
89
72
|
|
|
90
73
|
test('...', async ({ mount }) => {
|
|
91
74
|
await mount(RecipeSearchTestContainer, {
|
|
92
|
-
|
|
75
|
+
props: {
|
|
93
76
|
recipes: [
|
|
94
77
|
beer,
|
|
95
78
|
burger
|
|
@@ -140,24 +123,30 @@ We can also customize the shared `playwright/index.html` nearby.
|
|
|
140
123
|
### Specific Styles
|
|
141
124
|
|
|
142
125
|
If we want to load some specific styles for a single test, we might prefer using a test container component:
|
|
126
|
+
|
|
143
127
|
```ts
|
|
144
128
|
import styles from './some-styles.css';
|
|
129
|
+
|
|
145
130
|
@Component({
|
|
146
131
|
template: '<jc-greetings></jc-greetings>',
|
|
147
132
|
encapsulation: ViewEncapsulation.None,
|
|
148
133
|
styles: [styles]
|
|
149
134
|
})
|
|
150
|
-
class GreetingsTestContainer {
|
|
135
|
+
class GreetingsTestContainer {
|
|
136
|
+
}
|
|
151
137
|
```
|
|
152
138
|
|
|
153
139
|
### Angular Material & Angular Libraries with styles
|
|
154
140
|
|
|
155
141
|
As mentioned in [Versatile Angular Style Blog Post](https://marmicode.io/blog/versatile-angular),
|
|
156
|
-
Angular Material and other Angular libraries might use
|
|
142
|
+
Angular Material and other Angular libraries might use
|
|
143
|
+
a [Conditional "style" Export](https://nodejs.org/api/packages.html#conditional-exports)
|
|
157
144
|
that allows us to import prebuilt styles
|
|
158
|
-
_(
|
|
145
|
+
_(
|
|
146
|
+
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))_.
|
|
159
147
|
|
|
160
148
|
In that case, we can add the following configuration to our `playwright-ct.config.ts`:
|
|
149
|
+
|
|
161
150
|
```ts
|
|
162
151
|
const config: PlaywrightTestConfig = {
|
|
163
152
|
// ...
|
|
@@ -174,11 +163,13 @@ const config: PlaywrightTestConfig = {
|
|
|
174
163
|
```
|
|
175
164
|
|
|
176
165
|
## More examples
|
|
166
|
+
|
|
177
167
|
Cf. [/tests/playwright-ct-angular-wide/src](https://github.com/jscutlery/devkit/tree/main/tests/playwright-ct-angular-wide/src)
|
|
178
168
|
|
|
179
169
|
# ⚠️ Known Limitations
|
|
180
170
|
|
|
181
|
-
The way Playwright Component Testing works is different from the way things work with Karma, Jest, Vitest, Cypress
|
|
171
|
+
The way Playwright Component Testing works is different from the way things work with Karma, Jest, Vitest, Cypress
|
|
172
|
+
etc...
|
|
182
173
|
Playwright Component Testing tests run in a Node.js environment while the component is rendered in a browser.
|
|
183
174
|
|
|
184
175
|
This causes a couple of limitations as we can't directly access the `TestBed's` or the component's internals,
|
|
@@ -187,13 +178,15 @@ and we can only exchange serializable data with the component.
|
|
|
187
178
|
## 🪄 The Magic Behind the Scenes
|
|
188
179
|
|
|
189
180
|
The magical workaround behind the scenes is that at build time:
|
|
181
|
+
|
|
190
182
|
1. Playwright analyses all the calls to `mount()`,
|
|
191
183
|
2. it grabs the first parameter (the component class),
|
|
192
184
|
3. replaces the component class with a unique string (constructed from the component class name and es-module),
|
|
193
185
|
4. adds the component's es-module to Vite entrypoints,
|
|
194
186
|
5. and finally creates a map matching each unique string to the right es-module.
|
|
195
187
|
|
|
196
|
-
This way, when calling `mount()`, Playwright with communicate the unique string to the browser who will know which
|
|
188
|
+
This way, when calling `mount()`, Playwright with communicate the unique string to the browser who will know which
|
|
189
|
+
es-module to load.
|
|
197
190
|
|
|
198
191
|
Cf. https://youtu.be/y3YxX4sFJbM
|
|
199
192
|
Cf. https://github.com/microsoft/playwright/blob/cac67fb94f2c8a0ee82878054c39790e660f17ca/packages/playwright-test/src/tsxTransform.ts#L153
|
|
@@ -212,15 +205,17 @@ await mount(cmp);
|
|
|
212
205
|
|
|
213
206
|
```ts
|
|
214
207
|
// 🛑 this won't work
|
|
215
|
-
test(MyComponent.name, async ({ mount }) => {
|
|
208
|
+
test(MyComponent.name, async ({ mount }) => {
|
|
209
|
+
});
|
|
216
210
|
```
|
|
217
211
|
|
|
218
212
|
### ...declare components in the same file.
|
|
219
213
|
|
|
220
214
|
```ts
|
|
221
215
|
// 🛑 this won't work
|
|
222
|
-
@Component({...})
|
|
223
|
-
class GreetingsComponent {
|
|
216
|
+
@Component({ ... })
|
|
217
|
+
class GreetingsComponent {
|
|
218
|
+
}
|
|
224
219
|
|
|
225
220
|
test('<jc-greetings>', async ({ mount }) => {
|
|
226
221
|
await mount(GreetingsComponent);
|
|
@@ -228,20 +223,25 @@ test('<jc-greetings>', async ({ mount }) => {
|
|
|
228
223
|
```
|
|
229
224
|
|
|
230
225
|
### ...pass any symbol other than a component class
|
|
226
|
+
|
|
231
227
|
This makes the following impossible:
|
|
228
|
+
|
|
232
229
|
- passing providers to the `mount()` function
|
|
233
230
|
- passing modules to the `mount()` function (this is what currently makes the usage of mounting templates impossible)
|
|
234
231
|
- use non-standalone components
|
|
235
232
|
|
|
236
233
|
### ...use non-[Versatile Angular Style](https://marmicode.io/blog/versatile-angular)
|
|
234
|
+
|
|
237
235
|
We'll need a vite plugin to support traditional DI, external templates and stylesheets etc...
|
|
238
236
|
|
|
239
237
|
## 🔮 Future workarounds
|
|
240
238
|
|
|
241
239
|
If you really can't make it to [Versatile Angular Style](https://marmicode.io/blog/versatile-angular),
|
|
242
|
-
we can think of a couple of workarounds like using a vite plugin like **Brandon
|
|
240
|
+
we can think of a couple of workarounds like using a vite plugin like **Brandon
|
|
241
|
+
Roberts' [vite-plugin-angular](https://github.com/analogjs/analog/tree/main/packages/vite-plugin-angular)**
|
|
243
242
|
|
|
244
|
-
We could also implement some custom transform that registers the providers and import modules, like Playwright does for
|
|
243
|
+
We could also implement some custom transform that registers the providers and import modules, like Playwright does for
|
|
244
|
+
the component class.
|
|
245
245
|
|
|
246
246
|
# Setup
|
|
247
247
|
|
|
@@ -265,6 +265,7 @@ yarn add -D @jscutlery/playwright-ct-angular @playwright/test # or npm install -
|
|
|
265
265
|
## 🛠 Configure
|
|
266
266
|
|
|
267
267
|
Update `playwright-ct-config.ts` and replace:
|
|
268
|
+
|
|
268
269
|
```ts
|
|
269
270
|
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-react';
|
|
270
271
|
import { devices } from '@playwright/experimental-ct-react';
|
package/hooks.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/hooks";
|
package/index.cjs.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var experimentalCtCore = require('@playwright/experimental-ct-core');
|
|
9
|
-
var path = require('path');
|
|
5
|
+
var path = require('node:path');
|
|
6
|
+
var playwrightCtCore = require('@playwright/experimental-ct-core');
|
|
7
|
+
var test$1 = require('@playwright/test');
|
|
10
8
|
|
|
11
9
|
function _interopNamespace(e) {
|
|
12
10
|
if (e && e.__esModule) return e;
|
|
@@ -26,87 +24,44 @@ function _interopNamespace(e) {
|
|
|
26
24
|
return Object.freeze(n);
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
var playwright__namespace = /*#__PURE__*/_interopNamespace(playwright);
|
|
30
27
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
28
|
+
var playwrightCtCore__namespace = /*#__PURE__*/_interopNamespace(playwrightCtCore);
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
}, {}) ?? {};
|
|
61
|
-
/* 2. ...then use it as an output... */
|
|
62
|
-
await mount(component, {
|
|
63
|
-
...options,
|
|
64
|
-
outputs: {
|
|
65
|
-
...options.outputs,
|
|
66
|
-
...spies,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
/* Grab a locator to the TestBed's root element.
|
|
70
|
-
* We are loading a fresh new page for each test
|
|
71
|
-
* so the id should always be "root0".
|
|
72
|
-
* Otherwise, we'd have to hack our way and somehow grab all
|
|
73
|
-
* the `#root*`, sort them and grab the last one.
|
|
74
|
-
* As a matter of fact, we can't grab a reference to the fixture. */
|
|
75
|
-
const locator = page.locator('#root0');
|
|
76
|
-
/* Add spies to the returned locator. */
|
|
77
|
-
return Object.assign(locator, { spies });
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
const expect = playwright__namespace.expect;
|
|
82
|
-
const devices = playwright__namespace.devices;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Copyright (c) Microsoft Corporation.
|
|
86
|
-
*
|
|
87
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
88
|
-
* you may not use this file except in compliance with the License.
|
|
89
|
-
* You may obtain a copy of the License at
|
|
90
|
-
*
|
|
91
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
92
|
-
*
|
|
93
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
94
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
95
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
96
|
-
* See the License for the specific language governing permissions and
|
|
97
|
-
* limitations under the License.
|
|
98
|
-
*/
|
|
99
|
-
function plugin() {
|
|
100
|
-
/* Only fetch upon request to avoid resolution in workers. */
|
|
101
|
-
const { createPlugin,
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
103
|
-
} = require('@playwright/experimental-ct-core/lib/vitePlugin');
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
105
|
-
return createPlugin(path__namespace.join(__dirname, 'register-source.mjs'), () => { });
|
|
106
|
-
}
|
|
107
|
-
const defineConfig = (config) => experimentalCtCore.defineConfig({ ...config, _plugins: [plugin] });
|
|
30
|
+
/* @hack `test` is not exported in the type definition of `@playwright/experimental-ct-core`. */
|
|
31
|
+
// @eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const test = playwrightCtCore__namespace.test;
|
|
33
|
+
const defineConfig = (config, ...configs) => {
|
|
34
|
+
const original = playwrightCtCore__namespace.defineConfig({
|
|
35
|
+
...config,
|
|
36
|
+
'@playwright/test': {
|
|
37
|
+
packageJSON: require.resolve('./package.json'),
|
|
38
|
+
},
|
|
39
|
+
'@playwright/experimental-ct-core': {
|
|
40
|
+
registerSourceFile: path__namespace.join(__dirname, 'register-source.mjs'),
|
|
41
|
+
},
|
|
42
|
+
}, ...configs);
|
|
43
|
+
/* @hack for some weird reason, if we do not require the babel plugin here,
|
|
44
|
+
* babel loads an empty object instead of the default exported function. */
|
|
45
|
+
require('@jscutlery/playwright-ct-angular/transform-angular');
|
|
46
|
+
return {
|
|
47
|
+
...original,
|
|
48
|
+
'@playwright/test': {
|
|
49
|
+
...original['@playwright/test'],
|
|
50
|
+
babelPlugins: [
|
|
51
|
+
...original['@playwright/test'].babelPlugins,
|
|
52
|
+
[require.resolve('@jscutlery/playwright-ct-angular/transform-angular')],
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
};
|
|
108
57
|
|
|
58
|
+
Object.defineProperty(exports, 'devices', {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
get: function () { return test$1.devices; }
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(exports, 'expect', {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
get: function () { return test$1.expect; }
|
|
65
|
+
});
|
|
109
66
|
exports.defineConfig = defineConfig;
|
|
110
|
-
exports.devices = devices;
|
|
111
|
-
exports.expect = expect;
|
|
112
67
|
exports.test = test;
|
package/index.esm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/index.esm.js
CHANGED
|
@@ -1,84 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
|
|
4
|
-
import { defineConfig as defineConfig$1 } from '@playwright/experimental-ct-core';
|
|
5
|
-
import * as path from 'path';
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as playwrightCtCore from '@playwright/experimental-ct-core';
|
|
3
|
+
export { devices, expect } from '@playwright/test';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
}, {}) ?? {};
|
|
36
|
-
/* 2. ...then use it as an output... */
|
|
37
|
-
await mount(component, {
|
|
38
|
-
...options,
|
|
39
|
-
outputs: {
|
|
40
|
-
...options.outputs,
|
|
41
|
-
...spies,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
/* Grab a locator to the TestBed's root element.
|
|
45
|
-
* We are loading a fresh new page for each test
|
|
46
|
-
* so the id should always be "root0".
|
|
47
|
-
* Otherwise, we'd have to hack our way and somehow grab all
|
|
48
|
-
* the `#root*`, sort them and grab the last one.
|
|
49
|
-
* As a matter of fact, we can't grab a reference to the fixture. */
|
|
50
|
-
const locator = page.locator('#root0');
|
|
51
|
-
/* Add spies to the returned locator. */
|
|
52
|
-
return Object.assign(locator, { spies });
|
|
53
|
-
});
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
const expect = playwright.expect;
|
|
57
|
-
const devices = playwright.devices;
|
|
5
|
+
/* @hack `test` is not exported in the type definition of `@playwright/experimental-ct-core`. */
|
|
6
|
+
// @eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
const test = playwrightCtCore.test;
|
|
8
|
+
const defineConfig = (config, ...configs) => {
|
|
9
|
+
const original = playwrightCtCore.defineConfig({
|
|
10
|
+
...config,
|
|
11
|
+
'@playwright/test': {
|
|
12
|
+
packageJSON: require.resolve('./package.json'),
|
|
13
|
+
},
|
|
14
|
+
'@playwright/experimental-ct-core': {
|
|
15
|
+
registerSourceFile: path.join(__dirname, 'register-source.mjs'),
|
|
16
|
+
},
|
|
17
|
+
}, ...configs);
|
|
18
|
+
/* @hack for some weird reason, if we do not require the babel plugin here,
|
|
19
|
+
* babel loads an empty object instead of the default exported function. */
|
|
20
|
+
require('@jscutlery/playwright-ct-angular/transform-angular');
|
|
21
|
+
return {
|
|
22
|
+
...original,
|
|
23
|
+
'@playwright/test': {
|
|
24
|
+
...original['@playwright/test'],
|
|
25
|
+
babelPlugins: [
|
|
26
|
+
...original['@playwright/test'].babelPlugins,
|
|
27
|
+
[require.resolve('@jscutlery/playwright-ct-angular/transform-angular')],
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
};
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
* Copyright (c) Microsoft Corporation.
|
|
61
|
-
*
|
|
62
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
63
|
-
* you may not use this file except in compliance with the License.
|
|
64
|
-
* You may obtain a copy of the License at
|
|
65
|
-
*
|
|
66
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
67
|
-
*
|
|
68
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
69
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
70
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
71
|
-
* See the License for the specific language governing permissions and
|
|
72
|
-
* limitations under the License.
|
|
73
|
-
*/
|
|
74
|
-
function plugin() {
|
|
75
|
-
/* Only fetch upon request to avoid resolution in workers. */
|
|
76
|
-
const { createPlugin,
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
78
|
-
} = require('@playwright/experimental-ct-core/lib/vitePlugin');
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
80
|
-
return createPlugin(path.join(__dirname, 'register-source.mjs'), () => { });
|
|
81
|
-
}
|
|
82
|
-
const defineConfig = (config) => defineConfig$1({ ...config, _plugins: [plugin] });
|
|
83
|
-
|
|
84
|
-
export { defineConfig, devices, expect, test };
|
|
33
|
+
export { defineConfig, test };
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscutlery/playwright-ct-angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"dependencies": {
|
|
5
|
+
"@babel/core": "~7.23.0",
|
|
5
6
|
"jest-mock": "^28.1.3",
|
|
6
7
|
"rxjs": "^7.5.7",
|
|
7
8
|
"vite": "^3.2.1"
|
|
@@ -33,6 +34,11 @@
|
|
|
33
34
|
"module": "./register.esm.js",
|
|
34
35
|
"import": "./register.cjs.mjs",
|
|
35
36
|
"default": "./register.cjs.js"
|
|
37
|
+
},
|
|
38
|
+
"./transform-angular": {
|
|
39
|
+
"module": "./transform-angular.esm.js",
|
|
40
|
+
"import": "./transform-angular.cjs.mjs",
|
|
41
|
+
"default": "./transform-angular.cjs.js"
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
44
|
"module": "./index.esm.js",
|
package/register-source.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import 'zone.js';
|
|
|
3
3
|
import { getTestBed, TestBed } from '@angular/core/testing';
|
|
4
4
|
import {
|
|
5
5
|
BrowserDynamicTestingModule,
|
|
6
|
-
platformBrowserDynamicTesting
|
|
6
|
+
platformBrowserDynamicTesting
|
|
7
7
|
} from '@angular/platform-browser-dynamic/testing';
|
|
8
8
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|
9
9
|
import { Subscription } from 'rxjs';
|
|
@@ -32,29 +32,30 @@ export function pwRegister(components) {
|
|
|
32
32
|
let subscription;
|
|
33
33
|
|
|
34
34
|
globalThis.playwrightMount = async (component, rootElement, hooksConfig) => {
|
|
35
|
-
const
|
|
36
|
-
const cmpType = await loader();
|
|
35
|
+
const cmpType = component.type;
|
|
37
36
|
|
|
38
37
|
subscription = new Subscription();
|
|
39
38
|
|
|
40
39
|
TestBed.configureTestingModule({
|
|
41
|
-
imports: [BrowserAnimationsModule]
|
|
40
|
+
imports: [BrowserAnimationsModule]
|
|
42
41
|
});
|
|
43
42
|
const fixture = TestBed.createComponent(cmpType);
|
|
43
|
+
fixture.nativeElement.id = 'root';
|
|
44
44
|
|
|
45
45
|
/* Set inputs. */
|
|
46
|
-
for (const [name, value] of Object.entries(component.
|
|
46
|
+
for (const [name, value] of Object.entries(component.props ?? {})) {
|
|
47
47
|
fixture.componentInstance[name] = value;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/* Subscribe to outputs. */
|
|
51
51
|
for (const [name, callback] of Object.entries(
|
|
52
|
-
component.
|
|
52
|
+
component.on ?? {}
|
|
53
53
|
)) {
|
|
54
54
|
subscription.add(fixture.componentInstance[name].subscribe(callback));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
fixture.autoDetectChanges();
|
|
58
|
+
await fixture.whenStable();
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
globalThis.playwrightUnmount = async (rootElement) => {
|
package/register.cjs.js
CHANGED
|
@@ -31,29 +31,30 @@ function pwRegister(components) {
|
|
|
31
31
|
let subscription;
|
|
32
32
|
|
|
33
33
|
globalThis.playwrightMount = async (component, rootElement, hooksConfig) => {
|
|
34
|
-
const
|
|
35
|
-
const cmpType = await loader();
|
|
34
|
+
const cmpType = component.type;
|
|
36
35
|
|
|
37
36
|
subscription = new rxjs.Subscription();
|
|
38
37
|
|
|
39
38
|
testing.TestBed.configureTestingModule({
|
|
40
|
-
imports: [animations.BrowserAnimationsModule]
|
|
39
|
+
imports: [animations.BrowserAnimationsModule]
|
|
41
40
|
});
|
|
42
41
|
const fixture = testing.TestBed.createComponent(cmpType);
|
|
42
|
+
fixture.nativeElement.id = 'root';
|
|
43
43
|
|
|
44
44
|
/* Set inputs. */
|
|
45
|
-
for (const [name, value] of Object.entries(component.
|
|
45
|
+
for (const [name, value] of Object.entries(component.props ?? {})) {
|
|
46
46
|
fixture.componentInstance[name] = value;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/* Subscribe to outputs. */
|
|
50
50
|
for (const [name, callback] of Object.entries(
|
|
51
|
-
component.
|
|
51
|
+
component.on ?? {}
|
|
52
52
|
)) {
|
|
53
53
|
subscription.add(fixture.componentInstance[name].subscribe(callback));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
fixture.autoDetectChanges();
|
|
57
|
+
await fixture.whenStable();
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
globalThis.playwrightUnmount = async (rootElement) => {
|
package/register.esm.js
CHANGED
|
@@ -29,29 +29,30 @@ function pwRegister(components) {
|
|
|
29
29
|
let subscription;
|
|
30
30
|
|
|
31
31
|
globalThis.playwrightMount = async (component, rootElement, hooksConfig) => {
|
|
32
|
-
const
|
|
33
|
-
const cmpType = await loader();
|
|
32
|
+
const cmpType = component.type;
|
|
34
33
|
|
|
35
34
|
subscription = new Subscription();
|
|
36
35
|
|
|
37
36
|
TestBed.configureTestingModule({
|
|
38
|
-
imports: [BrowserAnimationsModule]
|
|
37
|
+
imports: [BrowserAnimationsModule]
|
|
39
38
|
});
|
|
40
39
|
const fixture = TestBed.createComponent(cmpType);
|
|
40
|
+
fixture.nativeElement.id = 'root';
|
|
41
41
|
|
|
42
42
|
/* Set inputs. */
|
|
43
|
-
for (const [name, value] of Object.entries(component.
|
|
43
|
+
for (const [name, value] of Object.entries(component.props ?? {})) {
|
|
44
44
|
fixture.componentInstance[name] = value;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/* Subscribe to outputs. */
|
|
48
48
|
for (const [name, callback] of Object.entries(
|
|
49
|
-
component.
|
|
49
|
+
component.on ?? {}
|
|
50
50
|
)) {
|
|
51
51
|
subscription.add(fixture.componentInstance[name].subscribe(callback));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
fixture.autoDetectChanges();
|
|
55
|
+
await fixture.whenStable();
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
globalThis.playwrightUnmount = async (rootElement) => {
|
package/src/index.d.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import { Type } from '@angular/core';
|
|
2
|
+
import type { PlaywrightTestConfig, TestType } from '@playwright/experimental-ct-core';
|
|
3
|
+
import * as playwright from '@playwright/test';
|
|
4
|
+
import type { Observable } from 'rxjs';
|
|
5
|
+
export type { PlaywrightTestConfig };
|
|
6
|
+
export { expect, devices } from '@playwright/test';
|
|
7
|
+
export interface ComponentFixtures {
|
|
8
|
+
mount<COMPONENT>(component: Type<COMPONENT>, options?: MountOptions<COMPONENT>): Promise<MountResult<COMPONENT>>;
|
|
9
|
+
}
|
|
10
|
+
export interface MountOptions<COMPONENT> {
|
|
11
|
+
props?: Partial<COMPONENT>;
|
|
12
|
+
on?: Outputs<COMPONENT>;
|
|
13
|
+
}
|
|
14
|
+
export type Outputs<COMPONENT> = Partial<{
|
|
15
|
+
[K in keyof COMPONENT]: COMPONENT[K] extends Observable<unknown> ? (value: Emitted<COMPONENT[K]>) => void : COMPONENT[K];
|
|
16
|
+
}>;
|
|
17
|
+
export interface MountResult<COMPONENT> extends playwright.Locator {
|
|
18
|
+
unmount(): Promise<void>;
|
|
19
|
+
update(options: {
|
|
20
|
+
props?: Partial<COMPONENT>;
|
|
21
|
+
on?: Outputs<COMPONENT>;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
type Emitted<OBSERVABLE> = OBSERVABLE extends Observable<infer EMITTED> ? EMITTED : OBSERVABLE;
|
|
25
|
+
export declare const test: TestType<ComponentFixtures>;
|
|
26
|
+
export declare const defineConfig: (config: any, ...configs: any[]) => PlaywrightTestConfig;
|