@rvoh/psychic-spec-helpers 0.2.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 +23 -0
- package/README.md +68 -0
- package/dist/esm/src/feature/helpers/launchBrowser.js +9 -0
- package/dist/esm/src/feature/helpers/launchPage.js +5 -0
- package/dist/esm/src/feature/helpers/launchViteServer.js +89 -0
- package/dist/esm/src/feature/helpers/providePuppeteerViteMatchers.js +73 -0
- package/dist/esm/src/feature/helpers/visit.js +6 -0
- package/dist/esm/src/feature/internal/evaluateWithRetryAndTimeout.js +29 -0
- package/dist/esm/src/feature/internal/evaluationFailure.js +6 -0
- package/dist/esm/src/feature/internal/evaluationSuccess.js +6 -0
- package/dist/esm/src/feature/internal/getAllTextContentFromPage.js +19 -0
- package/dist/esm/src/feature/internal/isPuppeteerPage.js +3 -0
- package/dist/esm/src/feature/internal/matchFailure.js +6 -0
- package/dist/esm/src/feature/internal/matchSuccess.js +6 -0
- package/dist/esm/src/feature/internal/requirePuppeteerPage.js +6 -0
- package/dist/esm/src/feature/matchers/toCheck.js +23 -0
- package/dist/esm/src/feature/matchers/toClick.js +25 -0
- package/dist/esm/src/feature/matchers/toClickButton.js +25 -0
- package/dist/esm/src/feature/matchers/toClickLink.js +25 -0
- package/dist/esm/src/feature/matchers/toClickSelector.js +25 -0
- package/dist/esm/src/feature/matchers/toFill.js +21 -0
- package/dist/esm/src/feature/matchers/toHaveChecked.js +19 -0
- package/dist/esm/src/feature/matchers/toHaveLink.js +25 -0
- package/dist/esm/src/feature/matchers/toHavePath.js +15 -0
- package/dist/esm/src/feature/matchers/toHaveSelector.js +14 -0
- package/dist/esm/src/feature/matchers/toHaveUnchecked.js +19 -0
- package/dist/esm/src/feature/matchers/toHaveUrl.js +14 -0
- package/dist/esm/src/feature/matchers/toMatchTextContent.js +16 -0
- package/dist/esm/src/feature/matchers/toNotHaveSelector.js +14 -0
- package/dist/esm/src/feature/matchers/toNotMatchTextContent.js +16 -0
- package/dist/esm/src/feature/matchers/toUncheck.js +23 -0
- package/dist/esm/src/index.js +12 -0
- package/dist/esm/src/shared/sleep.js +7 -0
- package/dist/esm/src/unit/SpecRequest.js +89 -0
- package/dist/esm/src/unit/SpecSession.js +48 -0
- package/dist/esm/src/unit/createPsychicServer.js +8 -0
- package/dist/esm/src/unit/supersession.js +72 -0
- package/dist/types/src/feature/helpers/launchBrowser.d.ts +2 -0
- package/dist/types/src/feature/helpers/launchPage.d.ts +2 -0
- package/dist/types/src/feature/helpers/launchViteServer.d.ts +6 -0
- package/dist/types/src/feature/helpers/providePuppeteerViteMatchers.d.ts +5 -0
- package/dist/types/src/feature/helpers/visit.d.ts +5 -0
- package/dist/types/src/feature/internal/evaluateWithRetryAndTimeout.d.ts +14 -0
- package/dist/types/src/feature/internal/evaluationFailure.d.ts +4 -0
- package/dist/types/src/feature/internal/evaluationSuccess.d.ts +4 -0
- package/dist/types/src/feature/internal/getAllTextContentFromPage.d.ts +2 -0
- package/dist/types/src/feature/internal/isPuppeteerPage.d.ts +1 -0
- package/dist/types/src/feature/internal/matchFailure.d.ts +4 -0
- package/dist/types/src/feature/internal/matchSuccess.d.ts +4 -0
- package/dist/types/src/feature/internal/requirePuppeteerPage.d.ts +1 -0
- package/dist/types/src/feature/matchers/toCheck.d.ts +5 -0
- package/dist/types/src/feature/matchers/toClick.d.ts +5 -0
- package/dist/types/src/feature/matchers/toClickButton.d.ts +5 -0
- package/dist/types/src/feature/matchers/toClickLink.d.ts +5 -0
- package/dist/types/src/feature/matchers/toClickSelector.d.ts +5 -0
- package/dist/types/src/feature/matchers/toFill.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHaveChecked.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHaveLink.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHavePath.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHaveSelector.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHaveUnchecked.d.ts +5 -0
- package/dist/types/src/feature/matchers/toHaveUrl.d.ts +5 -0
- package/dist/types/src/feature/matchers/toMatchTextContent.d.ts +5 -0
- package/dist/types/src/feature/matchers/toNotHaveSelector.d.ts +5 -0
- package/dist/types/src/feature/matchers/toNotMatchTextContent.d.ts +5 -0
- package/dist/types/src/feature/matchers/toUncheck.d.ts +5 -0
- package/dist/types/src/index.d.ts +45 -0
- package/dist/types/src/shared/sleep.d.ts +1 -0
- package/dist/types/src/unit/SpecRequest.d.ts +34 -0
- package/dist/types/src/unit/SpecSession.d.ts +29 -0
- package/dist/types/src/unit/createPsychicServer.d.ts +1 -0
- package/dist/types/src/unit/supersession.d.ts +6 -0
- package/package.json +47 -0
- package/src/feature/helpers/launchBrowser.ts +10 -0
- package/src/feature/helpers/launchPage.ts +7 -0
- package/src/feature/helpers/launchViteServer.ts +104 -0
- package/src/feature/helpers/providePuppeteerViteMatchers.ts +102 -0
- package/src/feature/helpers/visit.ts +11 -0
- package/src/feature/internal/evaluateWithRetryAndTimeout.ts +49 -0
- package/src/feature/internal/evaluationFailure.ts +6 -0
- package/src/feature/internal/evaluationSuccess.ts +6 -0
- package/src/feature/internal/getAllTextContentFromPage.ts +26 -0
- package/src/feature/internal/isPuppeteerPage.ts +3 -0
- package/src/feature/internal/matchFailure.ts +6 -0
- package/src/feature/internal/matchSuccess.ts +6 -0
- package/src/feature/internal/requirePuppeteerPage.ts +7 -0
- package/src/feature/matchers/toCheck.ts +34 -0
- package/src/feature/matchers/toClick.ts +30 -0
- package/src/feature/matchers/toClickButton.ts +30 -0
- package/src/feature/matchers/toClickLink.ts +30 -0
- package/src/feature/matchers/toClickSelector.ts +30 -0
- package/src/feature/matchers/toFill.ts +28 -0
- package/src/feature/matchers/toHaveChecked.ts +26 -0
- package/src/feature/matchers/toHaveLink.ts +30 -0
- package/src/feature/matchers/toHavePath.ts +23 -0
- package/src/feature/matchers/toHaveSelector.ts +21 -0
- package/src/feature/matchers/toHaveUnchecked.ts +26 -0
- package/src/feature/matchers/toHaveUrl.ts +21 -0
- package/src/feature/matchers/toMatchTextContent.ts +23 -0
- package/src/feature/matchers/toNotHaveSelector.ts +21 -0
- package/src/feature/matchers/toNotMatchTextContent.ts +26 -0
- package/src/feature/matchers/toUncheck.ts +35 -0
- package/src/index.ts +56 -0
- package/src/shared/sleep.ts +7 -0
- package/src/unit/SpecRequest.ts +160 -0
- package/src/unit/SpecSession.ts +103 -0
- package/src/unit/createPsychicServer.ts +8 -0
- package/src/unit/supersession.ts +100 -0
- package/tsconfig.json +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 RVOHealth
|
|
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.
|
|
22
|
+
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Psychic spec helpers
|
|
2
|
+
|
|
3
|
+
This repo provides spec helpers to be used in conjunction with the [psychic web framework](https://github.com/rvohealth/psychic).
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
1. Add this repo as a dev dependency in your psychic project (this is done by default for psychic apps, but worth mentioning in case it has been removed from your repo).
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add --dev @rvohealth/psychic-spec-helpers
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. import psychic spec helpers in your jest setup. This is automatically set up when provisioning a new psychic app, so you should only need to do this with an a-typical setup.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// spec/unit/setup/hooks.ts
|
|
17
|
+
import "@rvohealth/psychic-spec-helpers";
|
|
18
|
+
...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
3. import and use the provided spec helpers:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { specRequest as request } from '@rvohealth/psychic-spec-helpers'
|
|
25
|
+
import { PsychicController } from '@rvohealth/psychic'
|
|
26
|
+
|
|
27
|
+
describe('V1/Host/PlacesController', () => {
|
|
28
|
+
let user: User
|
|
29
|
+
let host: Host
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
await request.init(PsychicServer)
|
|
33
|
+
user = await createUser()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('GET index', () => {
|
|
37
|
+
function subject(expectedStatus: number = 200) {
|
|
38
|
+
return request.get('/v1/host/places', expectedStatus, {
|
|
39
|
+
headers: addEndUserAuthHeader(request, user, {}),
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
it('returns the index of Places', async () => {
|
|
44
|
+
const place = await createPlace({ style: 'cabin', name: 'My cabin' })
|
|
45
|
+
await createHostPlace({ host, place })
|
|
46
|
+
const results = (await subject()).body
|
|
47
|
+
|
|
48
|
+
expect(results).toEqual([
|
|
49
|
+
expect.objectContaining({
|
|
50
|
+
id: place.id,
|
|
51
|
+
style: 'cabin',
|
|
52
|
+
name: 'My cabin',
|
|
53
|
+
}),
|
|
54
|
+
])
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Questions?
|
|
61
|
+
|
|
62
|
+
- **Ask them on [Stack Overflow](https://stackoverflow.com)**, using the `[psychic]` tag.
|
|
63
|
+
|
|
64
|
+
## Contributing
|
|
65
|
+
|
|
66
|
+
Psychic is an open source library, so we encourage you to actively contribute. Visit our [Contributing](https://github.com/rvohealth/psychic-spec-helpers/CONTRIBUTING.md) guide to learn more about the processes we use for submitting pull requests or issues.
|
|
67
|
+
|
|
68
|
+
Are you trying to report a possible security vulnerability? Visit our [Security Policy](https://github.com/rvohealth/psychic-spec-helpers/SECURITY.md) for guidelines about how to proceed.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createServer } from 'net';
|
|
3
|
+
import sleep from '../../shared/sleep.js';
|
|
4
|
+
let serverProcess = undefined;
|
|
5
|
+
export default async function launchViteServer({ port = 3000, cmd = 'yarn client', timeout = 5000, } = {}) {
|
|
6
|
+
if (serverProcess)
|
|
7
|
+
return;
|
|
8
|
+
if (process.env.DEBUG === '1')
|
|
9
|
+
console.log('Starting server...');
|
|
10
|
+
const [_cmd, ...args] = cmd.split(' ');
|
|
11
|
+
serverProcess = spawn(_cmd, args, {
|
|
12
|
+
detached: true,
|
|
13
|
+
env: {
|
|
14
|
+
...process.env,
|
|
15
|
+
BROWSER: 'none',
|
|
16
|
+
VITE_PSYCHIC_ENV: 'test',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
await waitForPort(port, timeout);
|
|
20
|
+
serverProcess.stdout.on('data', data => {
|
|
21
|
+
if (process.env.DEBUG === '1')
|
|
22
|
+
console.log(`Server output: ${data}`);
|
|
23
|
+
});
|
|
24
|
+
serverProcess.on('error', err => {
|
|
25
|
+
throw err;
|
|
26
|
+
});
|
|
27
|
+
serverProcess.stdout.on('data', data => {
|
|
28
|
+
if (process.env.DEBUG === '1')
|
|
29
|
+
console.log(`Server output: ${data}`);
|
|
30
|
+
});
|
|
31
|
+
serverProcess.stderr.on('data', data => {
|
|
32
|
+
if (process.env.DEBUG === '1')
|
|
33
|
+
console.error(`Server error: ${data}`);
|
|
34
|
+
});
|
|
35
|
+
serverProcess.on('error', err => {
|
|
36
|
+
console.error(`Server process error: ${err}`);
|
|
37
|
+
});
|
|
38
|
+
serverProcess.on('close', code => {
|
|
39
|
+
if (process.env.DEBUG === '1')
|
|
40
|
+
console.log(`Server process exited with code ${code}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function stopViteServer() {
|
|
44
|
+
if (serverProcess?.pid) {
|
|
45
|
+
if (process.env.DEBUG === '1')
|
|
46
|
+
console.log('Stopping server...');
|
|
47
|
+
// serverProcess.kill('SIGINT')
|
|
48
|
+
process.kill(-serverProcess.pid, 'SIGKILL');
|
|
49
|
+
serverProcess = undefined;
|
|
50
|
+
if (process.env.DEBUG === '1')
|
|
51
|
+
console.log('server stopped');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function isPortAvailable(port) {
|
|
55
|
+
return new Promise(resolve => {
|
|
56
|
+
const server = createServer()
|
|
57
|
+
.once('error', err => {
|
|
58
|
+
if (err.code === 'EADDRINUSE') {
|
|
59
|
+
resolve(false);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
resolve(true);
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.once('listening', () => {
|
|
66
|
+
server.close();
|
|
67
|
+
resolve(true);
|
|
68
|
+
})
|
|
69
|
+
.listen(port, '127.0.0.1');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function waitForPort(port, timeout = 5000) {
|
|
73
|
+
if (await isPortAvailable(port)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
async function recursiveWaitForPort() {
|
|
78
|
+
if (await isPortAvailable(port)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (Date.now() > startTime + timeout) {
|
|
82
|
+
stopViteServer();
|
|
83
|
+
throw new Error('waited too long for port: ' + port);
|
|
84
|
+
}
|
|
85
|
+
await sleep(50);
|
|
86
|
+
return await recursiveWaitForPort();
|
|
87
|
+
}
|
|
88
|
+
return await recursiveWaitForPort();
|
|
89
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import toHaveSelector from '../matchers/toHaveSelector.js';
|
|
3
|
+
import toMatchTextContent from '../matchers/toMatchTextContent.js';
|
|
4
|
+
import toNotHaveSelector from '../matchers/toNotHaveSelector.js';
|
|
5
|
+
import toNotMatchTextContent from '../matchers/toNotMatchTextContent.js';
|
|
6
|
+
import toCheck from '../matchers/toCheck.js';
|
|
7
|
+
import toUncheck from '../matchers/toUncheck.js';
|
|
8
|
+
import toClick from '../matchers/toClick.js';
|
|
9
|
+
import toClickLink from '../matchers/toClickLink.js';
|
|
10
|
+
import toClickButton from '../matchers/toClickButton.js';
|
|
11
|
+
import toClickSelector from '../matchers/toClickSelector.js';
|
|
12
|
+
import toHavePath from '../matchers/toHavePath.js';
|
|
13
|
+
import toHaveUrl from '../matchers/toHaveUrl.js';
|
|
14
|
+
import toHaveLink from '../matchers/toHaveLink.js';
|
|
15
|
+
import toHaveChecked from '../matchers/toHaveChecked.js';
|
|
16
|
+
import toHaveUnchecked from '../matchers/toHaveUnchecked.js';
|
|
17
|
+
import toFill from '../matchers/toFill.js';
|
|
18
|
+
export default function providePuppeteerViteMatchers() {
|
|
19
|
+
;
|
|
20
|
+
global.expect.extend({
|
|
21
|
+
async toMatchTextContent(page, text) {
|
|
22
|
+
return await toMatchTextContent(page, text);
|
|
23
|
+
},
|
|
24
|
+
async toNotMatchTextContent(page, text) {
|
|
25
|
+
return await toNotMatchTextContent(page, text);
|
|
26
|
+
},
|
|
27
|
+
async toHaveSelector(page, cssSelector) {
|
|
28
|
+
return await toHaveSelector(page, cssSelector);
|
|
29
|
+
},
|
|
30
|
+
async toNotHaveSelector(page, cssSelector) {
|
|
31
|
+
return await toNotHaveSelector(page, cssSelector);
|
|
32
|
+
},
|
|
33
|
+
async toCheck(page, text) {
|
|
34
|
+
return await toCheck(page, text);
|
|
35
|
+
},
|
|
36
|
+
async toClick(page, text) {
|
|
37
|
+
return await toClick(page, text);
|
|
38
|
+
},
|
|
39
|
+
async toClickLink(page, text) {
|
|
40
|
+
return await toClickLink(page, text);
|
|
41
|
+
},
|
|
42
|
+
async toClickButton(page, text) {
|
|
43
|
+
return await toClickButton(page, text);
|
|
44
|
+
},
|
|
45
|
+
async toClickSelector(page, cssSelector) {
|
|
46
|
+
return await toClickSelector(page, cssSelector);
|
|
47
|
+
},
|
|
48
|
+
async toHavePath(page, path) {
|
|
49
|
+
return await toHavePath(page, path);
|
|
50
|
+
},
|
|
51
|
+
async toHaveUrl(page, url) {
|
|
52
|
+
return await toHaveUrl(page, url);
|
|
53
|
+
},
|
|
54
|
+
async toHaveChecked(page, text) {
|
|
55
|
+
return await toHaveChecked(page, text);
|
|
56
|
+
},
|
|
57
|
+
async toHaveUnchecked(page, checked) {
|
|
58
|
+
return await toHaveUnchecked(page, checked);
|
|
59
|
+
},
|
|
60
|
+
async toHaveLink(page, text) {
|
|
61
|
+
return await toHaveLink(page, text);
|
|
62
|
+
},
|
|
63
|
+
async toFill(page, cssSelector, text) {
|
|
64
|
+
return await toFill(page, cssSelector, text);
|
|
65
|
+
},
|
|
66
|
+
async toUncheck(page, text) {
|
|
67
|
+
return await toUncheck(page, text);
|
|
68
|
+
},
|
|
69
|
+
async toEvaluate(argumentPassedToExpect, evaluationFn, opts) {
|
|
70
|
+
return await evaluateWithRetryAndTimeout(argumentPassedToExpect, evaluationFn, opts);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import sleep from '../../shared/sleep.js';
|
|
2
|
+
export default async function evaluateWithRetryAndTimeout(
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
argumentPassedToExpect,
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
evaluationFn, opts) {
|
|
7
|
+
const timeout = opts.timeout || 4000;
|
|
8
|
+
const interval = opts.interval || 10;
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
async function intervalCb() {
|
|
11
|
+
const res = await evaluationFn(argumentPassedToExpect);
|
|
12
|
+
const expired = Date.now() >= startTime + timeout;
|
|
13
|
+
if (res.pass) {
|
|
14
|
+
return {
|
|
15
|
+
message: () => (opts.successText ? opts.successText(res.actual) : 'Success'),
|
|
16
|
+
pass: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (expired) {
|
|
20
|
+
return {
|
|
21
|
+
message: () => opts.failureText(res.actual),
|
|
22
|
+
pass: false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
await sleep(interval);
|
|
26
|
+
return await intervalCb();
|
|
27
|
+
}
|
|
28
|
+
return await intervalCb();
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default async function getAllTextContentFromPage(page) {
|
|
2
|
+
// Evaluate and extract all text content on the page
|
|
3
|
+
const allText = await page.evaluate(() => {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
7
|
+
const elements = document.body.querySelectorAll('*');
|
|
8
|
+
const textContentArray = [];
|
|
9
|
+
elements.forEach(element => {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
11
|
+
if (element.textContent.trim() !== '') {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
13
|
+
textContentArray.push(element.innerText.trim());
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return textContentArray.join(' ');
|
|
17
|
+
});
|
|
18
|
+
return allText;
|
|
19
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import isPuppeteerPage from './isPuppeteerPage.js';
|
|
2
|
+
export default function requirePuppeteerPage(argumentPassedToExpect) {
|
|
3
|
+
if (!isPuppeteerPage(argumentPassedToExpect)) {
|
|
4
|
+
throw new Error('Must pass a puppeteer page to expect when calling toMatchTextContent');
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
3
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
4
|
+
export default async function toCheck(page, expectedText) {
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
requirePuppeteerPage(page);
|
|
7
|
+
const checkbox = await page.$(`input[type="checkbox"][value="${expectedText}"]`);
|
|
8
|
+
if (!checkbox)
|
|
9
|
+
return evaluationFailure(`A checkbox was not found with "${expectedText}"`);
|
|
10
|
+
const isChecked = await page.evaluate(checkbox => checkbox.checked, checkbox);
|
|
11
|
+
if (isChecked)
|
|
12
|
+
return evaluationFailure(`A checkbox was found with "${expectedText}", but it is already checked`);
|
|
13
|
+
await checkbox.click();
|
|
14
|
+
const isCheckedNow = await page.evaluate(checkbox => checkbox.checked, checkbox);
|
|
15
|
+
return {
|
|
16
|
+
pass: isCheckedNow,
|
|
17
|
+
actual: expectedText,
|
|
18
|
+
};
|
|
19
|
+
}, {
|
|
20
|
+
successText: r => `Expected page to have checkable checkbox with text: "${expectedText}"`,
|
|
21
|
+
failureText: r => `Expected page not to have checkable checkbox with text: "${expectedText}"`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TimeoutError } from 'puppeteer';
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
5
|
+
export default async function toClick(page, expectedText) {
|
|
6
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
7
|
+
requirePuppeteerPage(page);
|
|
8
|
+
try {
|
|
9
|
+
const el = await page.locator(`::-p-text(${expectedText})`).setTimeout(5000).wait();
|
|
10
|
+
await el.click();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof TimeoutError)
|
|
14
|
+
return evaluationFailure(expectedText);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
pass: true,
|
|
19
|
+
actual: expectedText,
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
successText: () => `Expected page to have clickable element with text: "${expectedText}"`,
|
|
23
|
+
failureText: () => `Expected page not to have clickable element with text: "${expectedText}"`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TimeoutError } from 'puppeteer';
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
5
|
+
export default async function toClickButton(page, expectedText) {
|
|
6
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
7
|
+
requirePuppeteerPage(page);
|
|
8
|
+
try {
|
|
9
|
+
const el = await page.locator(`button ::-p-text(${expectedText})`).setTimeout(10).wait();
|
|
10
|
+
await el.click();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof TimeoutError)
|
|
14
|
+
return evaluationFailure(expectedText);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
pass: true,
|
|
19
|
+
actual: expectedText,
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
successText: () => `Expected page to have clickable button with text: "${expectedText}"`,
|
|
23
|
+
failureText: () => `Expected page not to have clickable button with text: "${expectedText}"`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TimeoutError } from 'puppeteer';
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
5
|
+
export default async function toClickLink(page, expectedText) {
|
|
6
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
7
|
+
requirePuppeteerPage(page);
|
|
8
|
+
try {
|
|
9
|
+
const el = await page.locator(`a ::-p-text(${expectedText})`).setTimeout(5000).wait();
|
|
10
|
+
await el.click();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof TimeoutError)
|
|
14
|
+
return evaluationFailure(expectedText);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
pass: true,
|
|
19
|
+
actual: expectedText,
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
successText: () => `Expected page to have clickable link with text: "${expectedText}"`,
|
|
23
|
+
failureText: () => `Expected page not to have clickable link with text: "${expectedText}"`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TimeoutError } from 'puppeteer';
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
5
|
+
export default async function toClickSelector(page, cssSelector) {
|
|
6
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
7
|
+
requirePuppeteerPage(page);
|
|
8
|
+
try {
|
|
9
|
+
const el = await page.locator(cssSelector).setTimeout(10).wait();
|
|
10
|
+
await el.click();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof TimeoutError)
|
|
14
|
+
return evaluationFailure(cssSelector);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
pass: true,
|
|
19
|
+
actual: cssSelector,
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
successText: () => `Expected page to have clickable selector with text: "${cssSelector}"`,
|
|
23
|
+
failureText: () => `Expected page not to have clickable selector with text: "${cssSelector}"`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
3
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
4
|
+
export default async function toFill(page, cssSelector, text) {
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
requirePuppeteerPage(page);
|
|
7
|
+
try {
|
|
8
|
+
await page.type(cssSelector, text);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return evaluationFailure(text);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
pass: true,
|
|
15
|
+
actual: text,
|
|
16
|
+
};
|
|
17
|
+
}, {
|
|
18
|
+
successText: () => `Expected page to have clickable link with text: "${text}"`,
|
|
19
|
+
failureText: () => `Expected page not to have clickable link with text: "${text}"`,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
3
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
4
|
+
export default async function toHaveChecked(page, expectedText) {
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
requirePuppeteerPage(page);
|
|
7
|
+
const checkbox = await page.$(`input[type="checkbox"][value="${expectedText}"]`);
|
|
8
|
+
if (!checkbox)
|
|
9
|
+
return evaluationFailure(`A checkbox was not found with "${expectedText}"`);
|
|
10
|
+
const isChecked = await page.evaluate(checkbox => checkbox.checked, checkbox);
|
|
11
|
+
return {
|
|
12
|
+
pass: isChecked,
|
|
13
|
+
actual: expectedText,
|
|
14
|
+
};
|
|
15
|
+
}, {
|
|
16
|
+
successText: r => `Expected page to have checked checkbox with text: "${expectedText}"`,
|
|
17
|
+
failureText: r => `Expected page not to have checked checkbox with text: "${expectedText}"`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TimeoutError } from 'puppeteer';
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
5
|
+
export default async function toHaveLink(page, expectedText) {
|
|
6
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
7
|
+
requirePuppeteerPage(page);
|
|
8
|
+
let el = undefined;
|
|
9
|
+
try {
|
|
10
|
+
el = await page.locator(`a ::-p-text(${expectedText})`).setTimeout(10).wait();
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err instanceof TimeoutError)
|
|
14
|
+
return evaluationFailure(expectedText);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
pass: !!el,
|
|
19
|
+
actual: expectedText,
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
successText: () => `Expected page to have clickable link with text: "${expectedText}"`,
|
|
23
|
+
failureText: () => `Expected page not to have clickable link with text: "${expectedText}"`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
3
|
+
export default async function toHavePath(page, expectedPath) {
|
|
4
|
+
requirePuppeteerPage(page);
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
const pathname = new URL(page.url()).pathname;
|
|
7
|
+
return {
|
|
8
|
+
pass: pathname === expectedPath,
|
|
9
|
+
actual: expectedPath,
|
|
10
|
+
};
|
|
11
|
+
}, {
|
|
12
|
+
successText: () => `Expected page to have path: "${expectedPath}"`,
|
|
13
|
+
failureText: () => `Expected page not to have path: "${expectedPath}"`,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
3
|
+
export default async function toHaveSelector(page, expectedSelector) {
|
|
4
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
5
|
+
requirePuppeteerPage(page);
|
|
6
|
+
return {
|
|
7
|
+
pass: !!(await page.$(expectedSelector)),
|
|
8
|
+
actual: expectedSelector,
|
|
9
|
+
};
|
|
10
|
+
}, {
|
|
11
|
+
successText: r => `Expected ${r} to have selector: ${expectedSelector}`,
|
|
12
|
+
failureText: r => `Expected ${r} not to have selector: ${expectedSelector}`,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import evaluationFailure from '../internal/evaluationFailure.js';
|
|
3
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
4
|
+
export default async function toHaveUnchecked(page, expectedText) {
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
requirePuppeteerPage(page);
|
|
7
|
+
const checkbox = await page.$(`input[type="checkbox"][value="${expectedText}"]`);
|
|
8
|
+
if (!checkbox)
|
|
9
|
+
return evaluationFailure(`A checkbox was not found with "${expectedText}"`);
|
|
10
|
+
const isChecked = await page.evaluate(checkbox => checkbox.checked, checkbox);
|
|
11
|
+
return {
|
|
12
|
+
pass: !isChecked,
|
|
13
|
+
actual: expectedText,
|
|
14
|
+
};
|
|
15
|
+
}, {
|
|
16
|
+
successText: r => `Expected page to have unchecked checkbox with text: "${expectedText}"`,
|
|
17
|
+
failureText: r => `Expected page not to have unchecked checkbox with text: "${expectedText}"`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
3
|
+
export default async function toHaveUrl(page, expectedUrl) {
|
|
4
|
+
requirePuppeteerPage(page);
|
|
5
|
+
return await evaluateWithRetryAndTimeout(page, async () => {
|
|
6
|
+
return {
|
|
7
|
+
pass: page.url() === expectedUrl,
|
|
8
|
+
actual: expectedUrl,
|
|
9
|
+
};
|
|
10
|
+
}, {
|
|
11
|
+
successText: () => `Expected page to have path: "${expectedUrl}"`,
|
|
12
|
+
failureText: () => `Expected page not to have path: "${expectedUrl}"`,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
|
|
2
|
+
import getAllTextContentFromPage from '../internal/getAllTextContentFromPage.js';
|
|
3
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
|
|
4
|
+
export default async function toMatchTextContent(argumentPassedToExpect, expected) {
|
|
5
|
+
return await evaluateWithRetryAndTimeout(argumentPassedToExpect, async () => {
|
|
6
|
+
requirePuppeteerPage(argumentPassedToExpect);
|
|
7
|
+
const actual = await getAllTextContentFromPage(argumentPassedToExpect);
|
|
8
|
+
return {
|
|
9
|
+
pass: actual.includes(expected),
|
|
10
|
+
actual,
|
|
11
|
+
};
|
|
12
|
+
}, {
|
|
13
|
+
successText: r => `Expected ${r} to match text ${expected}`,
|
|
14
|
+
failureText: r => `Expected ${r} not to match text ${expected}`,
|
|
15
|
+
});
|
|
16
|
+
}
|