nuxt-spec 0.1.18 → 0.2.0-alpha.2
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 +53 -10
- package/app/app.vue +2 -1
- package/app/components/NuxtSpecApiTestComponent.vue +24 -0
- package/bin/setup.js +20 -8
- package/config/index.mjs +35 -6
- package/config/merge.js +40 -0
- package/nuxt.config.ts +1 -1
- package/package.json +16 -6
- package/utils/e2e.ts +30 -0
- package/utils/index.d.ts +53 -0
- package/utils/index.ts +9 -0
- package/utils/screenshot.ts +34 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Nuxt Spec** (aka `nuxt-spec`) is a base layer for [Nuxt](https://nuxt.com/) applications incorporating together a couple of testing libraries and packages and providing some utility functions. I created this project in early 2025 because I was unable to find a convenient "one-dependency" way to start testing my Nuxt apps and I didn't want to repeat the same steps and maintain the same set of dependencies over and over.
|
|
6
6
|
|
|
7
|
-
While Nuxt itself does have a [dedicated module for testing](https://nuxt.com/docs/getting-started/testing), to remain as versatile as possible, it has to be combined with other packages (which can be different based on your choice). I am trying to overcome this by defining "
|
|
7
|
+
While Nuxt itself does have a [dedicated module for testing](https://nuxt.com/docs/getting-started/testing), to remain as versatile as possible, it has to be combined with other packages (which can be different based on your choice). I am trying to overcome this by defining "The Way". This is both the strength and the weakness of this project. You were warned.
|
|
8
8
|
|
|
9
9
|
The most important client of `nuxt-spec` is my [Nuxt Ignis](https://github.com/AloisSeckar/nuxt-ignis) template starter that adds up even more ready-to-use cool stuff for your future awesome Nuxt websites.
|
|
10
10
|
|
|
@@ -16,6 +16,7 @@ The `nuxt-spec` package comes with a built-in CLI tool that can help you:
|
|
|
16
16
|
- setup the dependency in your project
|
|
17
17
|
- scaffold the default `vitest.config.ts` (see [configuration](#configuration) section)
|
|
18
18
|
- add a few test-related script shorthands into your `package.json` (see [running tests](#running-tests) section)
|
|
19
|
+
- create demo test files in proposed file structure
|
|
19
20
|
|
|
20
21
|
To use it, just run the CLI script in your terminal:
|
|
21
22
|
|
|
@@ -36,7 +37,7 @@ If you don't want to use the CLI tool, or you want to understand its flow better
|
|
|
36
37
|
1) Add following dependency into your `package.json`:
|
|
37
38
|
|
|
38
39
|
```
|
|
39
|
-
"nuxt-spec": "0.
|
|
40
|
+
"nuxt-spec": "0.2.0-alpha.2"
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
2) Add following section into your `nuxt.config.ts`:
|
|
@@ -77,15 +78,18 @@ export default loadVitestConfig({
|
|
|
77
78
|
|
|
78
79
|
```
|
|
79
80
|
test/
|
|
81
|
+
├── browser/
|
|
82
|
+
│ └── vitest-browser.test.ts
|
|
80
83
|
├── e2e/
|
|
81
84
|
│ └── nuxt-e2e.test.ts
|
|
85
|
+
│ └── nuxt-visual.test.ts
|
|
82
86
|
├── nuxt/
|
|
83
87
|
│ └── nuxt-unit.test.ts
|
|
84
88
|
└── unit/
|
|
85
|
-
└── vitest.test.ts
|
|
89
|
+
└── vitest-unit.test.ts
|
|
86
90
|
```
|
|
87
91
|
|
|
88
|
-
You can use sample files from the [project repository](https://github.com/AloisSeckar/nuxt-spec/tree/v0.
|
|
92
|
+
You can use sample files from the [project repository](https://github.com/AloisSeckar/nuxt-spec/tree/v0.2.0-alpha.2/test).
|
|
89
93
|
|
|
90
94
|
### Install and execute
|
|
91
95
|
|
|
@@ -234,7 +238,8 @@ Or you can use the `vitest` command directly with all its parameters. See [Vites
|
|
|
234
238
|
|
|
235
239
|
**Nuxt Spec** currently contains:
|
|
236
240
|
- [vitest](https://www.npmjs.com/package/vitest) **v4** as the fundamental testing framework
|
|
237
|
-
- [@vitest/browser](https://www.npmjs.com/package/@vitest/browser) as
|
|
241
|
+
- [@vitest/browser](https://www.npmjs.com/package/@vitest/browser) as more advanced browser-native testing runner
|
|
242
|
+
- [@vitest/ui](https://www.npmjs.com/package/@vitest/ui) as graphic UI above the Vitest test runner
|
|
238
243
|
- [happy-dom](https://www.npmjs.com/package/happy-dom) as the headless browser runtime
|
|
239
244
|
- [playwright-core](https://www.npmjs.com/package/playwright-core) as the headless browser testing framework
|
|
240
245
|
- [@vue/test-utils](https://www.npmjs.com/package/@vue/test-utils) for testing Vue stuff
|
|
@@ -242,13 +247,13 @@ Or you can use the `vitest` command directly with all its parameters. See [Vites
|
|
|
242
247
|
|
|
243
248
|
Planned future development:
|
|
244
249
|
- reason about (not) using Vitest browser mode (or make it optional)
|
|
245
|
-
- solution for visual testing -
|
|
250
|
+
- solution for visual regression testing - (currently there is experimental custom solution)
|
|
246
251
|
|
|
247
|
-
See [CHANGELOG.md](https://github.com/AloisSeckar/nuxt-spec/blob/v0.
|
|
252
|
+
See [CHANGELOG.md](https://github.com/AloisSeckar/nuxt-spec/blob/v0.2.0-alpha.2/CHANGELOG.md) for the latest updates and features.
|
|
248
253
|
|
|
249
254
|
## Configuration
|
|
250
255
|
|
|
251
|
-
By default, `nuxt-spec` uses Vitest configuration defined in [`/config/index.mjs`](https://github.com/AloisSeckar/nuxt-spec/blob/v0.
|
|
256
|
+
By default, `nuxt-spec` uses Vitest configuration defined in [`/config/index.mjs`](https://github.com/AloisSeckar/nuxt-spec/blob/v0.2.0-alpha.2/config/index.mjs). The configuration is based on [Nuxt team recommendations](https://nuxt.com/docs/4.x/getting-started/testing) and our best judgement.
|
|
252
257
|
|
|
253
258
|
To add/override your custom config, you can create (or scaffold via CLI tool) a file named `vitest.config.ts` in the root of your project with the following content:
|
|
254
259
|
|
|
@@ -271,16 +276,19 @@ export default loadVitestConfig({
|
|
|
271
276
|
test: {
|
|
272
277
|
// your custom config specific to Vitest here
|
|
273
278
|
}
|
|
279
|
+
// by the nature of the Vitest config resolution,
|
|
280
|
+
// you may also pass ANY OTHER valid Vite configuration options here
|
|
274
281
|
})
|
|
275
282
|
```
|
|
276
283
|
|
|
277
|
-
By default, Nuxt Spec built-in configuration establishes
|
|
284
|
+
By default, Nuxt Spec built-in configuration establishes 4 `projects` + one fallback:
|
|
278
285
|
- `unit` - for unit tests in `test/unit/**` - env is set to `node`
|
|
279
286
|
- `nuxt` - for Nuxt-related tests in `test/nuxt/**` - env is set to `nuxt`
|
|
280
287
|
- `e2e` - for end-to-end tests in `test/e2e/**` - env is set to `node`
|
|
288
|
+
- `browser` - for browser-mode tests in `test/browser/**` - env is set to `node` (this is effectively an alternative to `nuxt` relying on `@vitest/browser` instead of `@nuxt/test-utils`)
|
|
281
289
|
- `default` - fallback for all other tests in `test/**` and/or `tests/**` directories - env is set to `node`
|
|
282
290
|
|
|
283
|
-
Vitest will then expects at least one test defined in either of those directories.
|
|
291
|
+
Vitest will then expects at least one test defined in either of those directories. Any parts of the `test.projects` confing may be altered and user-defined values will be logically merged with the defaults. Also you may add new custom projects' definitions to fit your needs. If your project uses significantly different configuration (i.e. your tests reside in completely different path), you can pass `false` as a second parameter to `loadVitestConfig()` function to exclude default `test.projects` values from being injected completely:
|
|
284
292
|
|
|
285
293
|
```ts
|
|
286
294
|
import { loadVitestConfig } from 'nuxt-spec/config'
|
|
@@ -292,6 +300,41 @@ export default loadVitestConfig({
|
|
|
292
300
|
|
|
293
301
|
Alternatively, if you don't want to use any part of the `nuxt-spec` default configuration at all, you can override `vitest.config.ts` file completely and define your own [Vitest configuration](https://vitest.dev/config/) from scratch.
|
|
294
302
|
|
|
303
|
+
## Utilities
|
|
304
|
+
|
|
305
|
+
Nuxt Spec offers couple of utility functions that are exported via `nuxt-spec/utils` subpackage.
|
|
306
|
+
|
|
307
|
+
You can use them in your test files as follows:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { compareScreenshot, gotoPage, getDataHtml, getAPIResultHtml, } from 'nuxt-spec/utils'
|
|
311
|
+
|
|
312
|
+
// accepts instance of NuxtPage (from @nuxt/test-utils)
|
|
313
|
+
// takes a screenshot of current viewport and compares it with stored baseline
|
|
314
|
+
// if screenshot doesn't exist, it will be created as baseline
|
|
315
|
+
// if screenshots don't match, the method will cause Vitest test to fail
|
|
316
|
+
await compareScreenshot(page, 'screenshot.png')
|
|
317
|
+
|
|
318
|
+
// navigates to given URL and returns the instance of NuxtPage (from @nuxt/test-utils)
|
|
319
|
+
const page: NuxtPage = await gotoPage('url')
|
|
320
|
+
|
|
321
|
+
// accepts either a URL string or instance of NuxtPage (from @nuxt/test-utils) and a CSS selector
|
|
322
|
+
// returns `innerHTML` of the element matching the selector
|
|
323
|
+
const html: string = await getDataHtml('/', '#test')
|
|
324
|
+
const html: string = await getDataHtml(page, '#test')
|
|
325
|
+
|
|
326
|
+
// accepts either a URL string or instance of NuxtPage (from @nuxt/test-utils)
|
|
327
|
+
// css selector for element that triggers API call when clicked (i.e. button)
|
|
328
|
+
// fragment of API endpoint URL that should be called (to test the response)
|
|
329
|
+
// css selector for element where the API response should be rendered (i.e. div)
|
|
330
|
+
// returns `innerHTML` of the element matching the result selector after the API call
|
|
331
|
+
// is made by Playwright runner
|
|
332
|
+
const html: string = await getAPIResultHtml('/', '#api-fetch', '/your-api', '#api-result')
|
|
333
|
+
const html: string = await getAPIResultHtml(page, '#api-fetch', '/your-api', '#api-result')
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
For detailed description, see [utils.d.ts](https://github.com/AloisSeckar/nuxt-spec/blob/v0.2.0-alpha.2/utils/index.d.ts).
|
|
337
|
+
|
|
295
338
|
## Contact
|
|
296
339
|
|
|
297
340
|
Use GitHub issues to report bugs or suggest improvements. I will be more than happy to address them.
|
package/app/app.vue
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h2>API Test</h2>
|
|
4
|
+
<button id="api-fetch" @click="fetchData">
|
|
5
|
+
Fetch Data
|
|
6
|
+
</button>
|
|
7
|
+
<div id="api-result">
|
|
8
|
+
{{ data }}
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
const data = ref('')
|
|
15
|
+
const fetchData = async () => {
|
|
16
|
+
try {
|
|
17
|
+
data.value = await $fetch('https://jsonplaceholder.typicode.com/posts/1')
|
|
18
|
+
console.debug('Fetched data:', data.value)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
data.value = 'Error fetching data'
|
|
21
|
+
console.error('API fetch error:', error)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
</script>
|
package/bin/setup.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function specSetup(autoRun = false) {
|
|
|
37
37
|
// add nuxt-spec
|
|
38
38
|
try {
|
|
39
39
|
await updateJsonFile('package.json', 'dependencies', {
|
|
40
|
-
'nuxt-spec': '0.
|
|
40
|
+
'nuxt-spec': '0.2.0-alpha.2',
|
|
41
41
|
}, isAutoRun, 'This will add \'nuxt-spec\' dependency to your \'package.json\'. Continue?')
|
|
42
42
|
} catch (error) {
|
|
43
43
|
console.error('Error adding \'nuxt-spec\' dependency:\n', error.message)
|
|
@@ -107,7 +107,7 @@ export async function specSetup(autoRun = false) {
|
|
|
107
107
|
if (pathExists('.npmrc')) {
|
|
108
108
|
await updateTextFile('.npmrc', ['shamefully-hoist=true'], isAutoRun, 'This will adjust \'.npmrc\' file in your project. Continue?')
|
|
109
109
|
} else {
|
|
110
|
-
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.
|
|
110
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/.npmrc',
|
|
111
111
|
'.npmrc', isAutoRun, 'This will add \'.npmrc\' file for your project. Continue?')
|
|
112
112
|
}
|
|
113
113
|
} catch (error) {
|
|
@@ -117,7 +117,7 @@ export async function specSetup(autoRun = false) {
|
|
|
117
117
|
|
|
118
118
|
// 4) create vitest.config.ts
|
|
119
119
|
try {
|
|
120
|
-
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.
|
|
120
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/config/vitest.config.ts.template',
|
|
121
121
|
'vitest.config.ts', isAutoRun, 'This will create a new \'vitest.config.ts\' file for your project. Continue?')
|
|
122
122
|
} catch (error) {
|
|
123
123
|
console.error('Error setting up \'vitest.config.ts\':\n', error.message)
|
|
@@ -155,22 +155,34 @@ export async function specSetup(autoRun = false) {
|
|
|
155
155
|
const createSampleTests = isAutoRun || await promptUser('Do you want to create sample tests in \'/test\' folder?')
|
|
156
156
|
if (createSampleTests) {
|
|
157
157
|
try {
|
|
158
|
-
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.
|
|
158
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/test/browser/vitest-browser.test.ts',
|
|
159
|
+
'test/browser/vitest-browser.test.ts', true)
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Error setting up \'vitest-browser.test.ts\':\n', error.message)
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/test/e2e/nuxt-e2e.test.ts',
|
|
159
165
|
'test/e2e/nuxt-e2e.test.ts', true)
|
|
160
166
|
} catch (error) {
|
|
161
167
|
console.error('Error setting up \'nuxt-e2e.test.ts\':\n', error.message)
|
|
162
168
|
}
|
|
163
169
|
try {
|
|
164
|
-
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.
|
|
170
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/test/e2e/nuxt-visual.test.ts',
|
|
171
|
+
'test/e2e/nuxt-visual.test.ts', true)
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('Error setting up \'nuxt-visual.test.ts\':\n', error.message)
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/test/nuxt/nuxt-unit.test.ts',
|
|
165
177
|
'test/nuxt/nuxt-unit.test.ts', true)
|
|
166
178
|
} catch (error) {
|
|
167
179
|
console.error('Error setting up \'nuxt-unit.test.ts\':\n', error.message)
|
|
168
180
|
}
|
|
169
181
|
try {
|
|
170
|
-
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.
|
|
171
|
-
'test/unit/vitest.test.ts', true)
|
|
182
|
+
await createFileFromWebTemplate('https://raw.githubusercontent.com/AloisSeckar/nuxt-spec/refs/tags/v0.2.0-alpha.2/test/unit/vitest-unit.test.ts',
|
|
183
|
+
'test/unit/vitest-unit.test.ts', true)
|
|
172
184
|
} catch (error) {
|
|
173
|
-
console.error('Error setting up \'vitest.test.ts\':\n', error.message)
|
|
185
|
+
console.error('Error setting up \'vitest-unit.test.ts\':\n', error.message)
|
|
174
186
|
}
|
|
175
187
|
}
|
|
176
188
|
|
package/config/index.mjs
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// based on https://nuxt.com/docs/4.x/getting-started/testing#setup
|
|
3
3
|
// `projects=false` can be used to suspend the default usage of "projects" in Vitest config
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { mergeConfig } from './merge.js' // defu-based merge function
|
|
6
6
|
import { defineConfig } from 'vitest/config'
|
|
7
7
|
import { defineVitestProject } from '@nuxt/test-utils/config'
|
|
8
|
+
import { playwright } from '@vitest/browser-playwright'
|
|
9
|
+
import vue from '@vitejs/plugin-vue'
|
|
8
10
|
|
|
9
11
|
export async function loadVitestConfig(userVitestConfig, projects = true) {
|
|
10
12
|
const baseConfig = {
|
|
@@ -17,19 +19,19 @@ export async function loadVitestConfig(userVitestConfig, projects = true) {
|
|
|
17
19
|
{
|
|
18
20
|
test: {
|
|
19
21
|
name: 'default',
|
|
20
|
-
include: ['{test,tests}/**/*.{test,spec}.ts', '!test/{
|
|
22
|
+
include: ['{test,tests}/**/*.{test,spec}.ts', '!test/{browser,e2e,nuxt,unit}/**'],
|
|
21
23
|
environment: 'node',
|
|
22
24
|
},
|
|
23
25
|
},
|
|
24
|
-
// proposed setup for
|
|
26
|
+
// proposed setup for Unit tests
|
|
25
27
|
{
|
|
26
28
|
test: {
|
|
27
29
|
name: 'node',
|
|
28
|
-
include: ['test/
|
|
30
|
+
include: ['test/unit/**/*.{test,spec}.ts'],
|
|
29
31
|
environment: 'node',
|
|
30
32
|
},
|
|
31
33
|
},
|
|
32
|
-
// proposed setup for Nuxt
|
|
34
|
+
// proposed setup for Nuxt component tests
|
|
33
35
|
await defineVitestProject({
|
|
34
36
|
test: {
|
|
35
37
|
name: 'nuxt',
|
|
@@ -37,8 +39,35 @@ export async function loadVitestConfig(userVitestConfig, projects = true) {
|
|
|
37
39
|
environment: 'nuxt',
|
|
38
40
|
},
|
|
39
41
|
}),
|
|
42
|
+
// proposed setup for classic E2E tests (node-based, using @nuxt/test-utils)
|
|
43
|
+
{
|
|
44
|
+
test: {
|
|
45
|
+
name: 'e2e',
|
|
46
|
+
include: ['test/e2e/**/*.{test,spec}.ts'],
|
|
47
|
+
environment: 'node',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// proposed setup for browser component tests (with Playwright runner)
|
|
51
|
+
{
|
|
52
|
+
// vue plugin is required for proper imports resolution
|
|
53
|
+
plugins: [vue()],
|
|
54
|
+
test: {
|
|
55
|
+
name: 'browser',
|
|
56
|
+
include: ['test/browser/**/*.{test,spec}.ts'],
|
|
57
|
+
environment: 'node',
|
|
58
|
+
browser: {
|
|
59
|
+
provider: playwright(),
|
|
60
|
+
enabled: true,
|
|
61
|
+
headless: true,
|
|
62
|
+
instances: [{
|
|
63
|
+
browser: 'chromium',
|
|
64
|
+
viewport: { width: 1280, height: 720 },
|
|
65
|
+
}],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
40
69
|
]
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
return
|
|
72
|
+
return mergeConfig(userVitestConfig, defineConfig(baseConfig))
|
|
44
73
|
}
|
package/config/merge.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// custom merger function based on defu
|
|
2
|
+
// allows working with the "projects" array properly
|
|
3
|
+
// user-defined config overrides are merged by "name"
|
|
4
|
+
|
|
5
|
+
// for consistent and predictable results, passing "include" or "browser.instances"
|
|
6
|
+
// will result into OVERRIDE insteead of merging with nuxt-spec defaults
|
|
7
|
+
|
|
8
|
+
import { createDefu } from 'defu'
|
|
9
|
+
|
|
10
|
+
export const mergeConfig = createDefu((obj, key, value) => {
|
|
11
|
+
if (key === 'projects' && Array.isArray(obj[key]) && Array.isArray(value)) {
|
|
12
|
+
const defaults = obj[key]
|
|
13
|
+
const overrides = value
|
|
14
|
+
|
|
15
|
+
// override default values if user-defined config specifies them
|
|
16
|
+
obj[key] = defaults.map((defaultProject) => {
|
|
17
|
+
const override = overrides.find(o => o.name === defaultProject.name)
|
|
18
|
+
return override ? mergeProject(override, defaultProject) : defaultProject
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// add any user projects that don't exist in defaults
|
|
22
|
+
for (const override of overrides) {
|
|
23
|
+
if (!defaults.some(d => d.name === override.name)) {
|
|
24
|
+
obj[key].push(override)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Keys where user value should fully replace the default, not merge
|
|
33
|
+
const overrideKeys = new Set(['include', 'instances'])
|
|
34
|
+
|
|
35
|
+
const mergeProject = createDefu((obj, key, value) => {
|
|
36
|
+
if (overrideKeys.has(key)) {
|
|
37
|
+
obj[key] = value
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
})
|
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-spec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.2",
|
|
4
4
|
"description": "Test-pack layer for Nuxt Applications",
|
|
5
5
|
"repository": "github:AloisSeckar/nuxt-spec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,27 +15,37 @@
|
|
|
15
15
|
"types": "./config/index.d.ts",
|
|
16
16
|
"import": "./config/index.mjs",
|
|
17
17
|
"default": "./config/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./utils": {
|
|
20
|
+
"types": "./utils/index.d.ts",
|
|
21
|
+
"import": "./utils/index.ts",
|
|
22
|
+
"default": "./utils/index.ts"
|
|
18
23
|
}
|
|
19
24
|
},
|
|
20
25
|
"files": [
|
|
21
26
|
"app",
|
|
22
27
|
"bin",
|
|
23
28
|
"config",
|
|
24
|
-
"public"
|
|
29
|
+
"public",
|
|
30
|
+
"utils"
|
|
25
31
|
],
|
|
26
32
|
"dependencies": {
|
|
27
|
-
"@nuxt/eslint": "1.
|
|
33
|
+
"@nuxt/eslint": "1.15.1",
|
|
28
34
|
"@nuxt/test-utils": "4.0.0",
|
|
35
|
+
"@vitejs/plugin-vue": "6.0.4",
|
|
29
36
|
"@vitest/browser": "4.0.18",
|
|
37
|
+
"@vitest/browser-playwright": "4.0.18",
|
|
38
|
+
"@vitest/ui": "4.0.18",
|
|
30
39
|
"@vue/test-utils": "2.4.6",
|
|
31
40
|
"elrh-cosca": "0.3.5",
|
|
32
|
-
"happy-dom": "20.
|
|
41
|
+
"happy-dom": "20.7.0",
|
|
33
42
|
"nuxt": "4.3.1",
|
|
34
43
|
"playwright-core": "1.58.2",
|
|
35
44
|
"typescript": "5.9.3",
|
|
36
45
|
"vitest": "4.0.18",
|
|
37
|
-
"vue": "
|
|
38
|
-
"vue
|
|
46
|
+
"vitest-browser-vue": "2.0.2",
|
|
47
|
+
"vue": "3.5.28",
|
|
48
|
+
"vue-router": "5.0.3"
|
|
39
49
|
},
|
|
40
50
|
"scripts": {
|
|
41
51
|
"analyze": "nuxt analyze",
|
package/utils/e2e.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createPage, url } from '@nuxt/test-utils/e2e'
|
|
2
|
+
import type { NuxtPage } from '@nuxt/test-utils'
|
|
3
|
+
|
|
4
|
+
// visit a specified URL and return the page instance for further interaction
|
|
5
|
+
export async function gotoPage(pageName: string): Promise<NuxtPage> {
|
|
6
|
+
const page = await createPage()
|
|
7
|
+
const urlPath = pageName.startsWith('/') ? url(pageName) : url(`/${pageName}`)
|
|
8
|
+
await page.goto(urlPath, { waitUntil: 'hydration' })
|
|
9
|
+
return page
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// extract HTML content from specified element on a given page
|
|
13
|
+
export async function getDataHtml(page: NuxtPage | string, element: string): Promise<string> {
|
|
14
|
+
const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
|
|
15
|
+
const dataDiv = pageInstance.locator(element)
|
|
16
|
+
return await dataDiv.innerHTML()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// execute an API call and extract HTML content from the result
|
|
20
|
+
// (assumes clickable element that triggers the request
|
|
21
|
+
// and separate element for displaying the response)
|
|
22
|
+
export async function getAPIResultHtml(page: NuxtPage | string, triggerElement: string, targetUrl: string, responseElement: string) {
|
|
23
|
+
const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
|
|
24
|
+
await pageInstance.click(triggerElement)
|
|
25
|
+
await pageInstance.waitForResponse(response =>
|
|
26
|
+
response.url().includes(targetUrl) && response.ok(),
|
|
27
|
+
)
|
|
28
|
+
const resultDiv = pageInstance.locator(responseElement)
|
|
29
|
+
return await resultDiv.innerHTML()
|
|
30
|
+
}
|
package/utils/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { NuxtPage } from '@nuxt/test-utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Visit a specified URL and return the page instance for further interaction.
|
|
5
|
+
*
|
|
6
|
+
* @param pageName - Path segment appended to the base URL (e.g. `'about'` → `/<about>`)
|
|
7
|
+
* @returns Playwright page instance after navigation and hydration
|
|
8
|
+
*/
|
|
9
|
+
export declare function gotoPage(pageName: string): Promise<NuxtPage>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract inner HTML content from a specified element on a given page.
|
|
13
|
+
*
|
|
14
|
+
* @param page - Playwright page instance, or a page name string (will call `gotoPage` internally)
|
|
15
|
+
* @param element - CSS selector identifying the target element
|
|
16
|
+
* @returns The inner HTML of the matched element
|
|
17
|
+
*/
|
|
18
|
+
export declare function getDataHtml(page: NuxtPage | string, element: string): Promise<string>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute an API call by clicking a trigger element, wait for a successful
|
|
22
|
+
* response matching the target URL, then extract the inner HTML from the
|
|
23
|
+
* response element.
|
|
24
|
+
*
|
|
25
|
+
* @param page - Playwright page instance, or a page name string (will call `gotoPage` internally)
|
|
26
|
+
* @param triggerElement - CSS selector for the clickable element that triggers the API request
|
|
27
|
+
* @param targetUrl - Substring matched against the response URL to identify the expected API call
|
|
28
|
+
* @param responseElement - CSS selector for the element displaying the API response
|
|
29
|
+
* @returns The inner HTML of the response element
|
|
30
|
+
*/
|
|
31
|
+
export declare function getAPIResultHtml(
|
|
32
|
+
page: NuxtPage | string,
|
|
33
|
+
triggerElement: string,
|
|
34
|
+
targetUrl: string,
|
|
35
|
+
responseElement: string,
|
|
36
|
+
): Promise<string>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Capture a full-page screenshot and compare it against a stored baseline PNG.
|
|
40
|
+
* When run with `-u` / `--update`, or when no baseline exists yet, the current
|
|
41
|
+
* screenshot is saved as the new baseline.
|
|
42
|
+
*
|
|
43
|
+
* @param page - Playwright page instance obtained from `createPage()`
|
|
44
|
+
* @param fileName - Name of the PNG file used for baseline storage and comparison
|
|
45
|
+
* @param targetDir - Directory for baseline/current screenshots, relative to project root (defaults to `test/e2e`)
|
|
46
|
+
* @returns `true` when the screenshot matches the baseline (or a new baseline was saved)
|
|
47
|
+
* @throws Fails the current Vitest test when a mismatch is detected
|
|
48
|
+
*/
|
|
49
|
+
export declare function compareScreenshot(
|
|
50
|
+
page: NuxtPage,
|
|
51
|
+
fileName: string,
|
|
52
|
+
targetDir?: string,
|
|
53
|
+
): Promise<boolean>
|
package/utils/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import type { NuxtPage } from '@nuxt/test-utils'
|
|
4
|
+
import { expect } from 'vitest'
|
|
5
|
+
|
|
6
|
+
export async function compareScreenshot(page: NuxtPage, fileName: string, targetDir?: string): Promise<boolean> {
|
|
7
|
+
const dir = resolve(process.cwd(), targetDir ?? 'test/e2e')
|
|
8
|
+
const baselineDir = resolve(dir, '__baseline__')
|
|
9
|
+
const currentDir = resolve(dir, '__current__')
|
|
10
|
+
|
|
11
|
+
// capture full-page screenshot as PNG
|
|
12
|
+
const screenshot = await page.screenshot({ fullPage: true })
|
|
13
|
+
const baselinePath = resolve(baselineDir, fileName)
|
|
14
|
+
|
|
15
|
+
const updating = expect.getState().snapshotState?._updateSnapshot === 'all'
|
|
16
|
+
if (updating || !existsSync(baselinePath)) {
|
|
17
|
+
// save new baseline screenshot
|
|
18
|
+
mkdirSync(baselineDir, { recursive: true })
|
|
19
|
+
writeFileSync(baselinePath, screenshot)
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// compare against stored baseline PNG
|
|
24
|
+
const baseline = readFileSync(baselinePath)
|
|
25
|
+
if (!screenshot.equals(baseline)) {
|
|
26
|
+
// save what the test actually saw for debugging
|
|
27
|
+
mkdirSync(currentDir, { recursive: true })
|
|
28
|
+
const actualPath = resolve(currentDir, fileName)
|
|
29
|
+
writeFileSync(actualPath, screenshot)
|
|
30
|
+
expect.fail(`Screenshot mismatch. Actual result saved to: ${actualPath}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true
|
|
34
|
+
}
|