@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/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@rvoh/psychic-spec-helpers",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "psychic framework spec helpers",
|
|
6
|
+
"main": "./dist/cjs/src/index.js",
|
|
7
|
+
"module": "./dist/esm/src/index.js",
|
|
8
|
+
"types": "./dist/types/src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"require": "./dist/cjs/src/index.js",
|
|
12
|
+
"import": "./dist/esm/src/index.js",
|
|
13
|
+
"types": "./dist/types/src/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"tsconfig.json",
|
|
18
|
+
"src/**/*",
|
|
19
|
+
"dist/**/*"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "echo \"building psychic-spec-helpers...\" && rm -rf dist && npx tsc -p ./tsconfig.json",
|
|
23
|
+
"prepack": "yarn build"
|
|
24
|
+
},
|
|
25
|
+
"packageManager": "yarn@4.4.1",
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@types/node": "*",
|
|
28
|
+
"@types/supertest": "*",
|
|
29
|
+
"puppeteer": "*",
|
|
30
|
+
"supertest": "*",
|
|
31
|
+
"ts-node": "*",
|
|
32
|
+
"typescript": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/cookiejar": "^2",
|
|
36
|
+
"@types/node": "^22.5.1",
|
|
37
|
+
"@types/supertest": "^6.0.2",
|
|
38
|
+
"puppeteer": "^24.4.0",
|
|
39
|
+
"supertest": "^7.0.0",
|
|
40
|
+
"ts-node": "^10.9.2",
|
|
41
|
+
"typescript": "^5.5.4",
|
|
42
|
+
"vitest": "^3.0.8"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"cookiejar": "^2.1.4"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
|
2
|
+
import { createServer } from 'net'
|
|
3
|
+
import sleep from '../../shared/sleep.js'
|
|
4
|
+
|
|
5
|
+
let serverProcess: ChildProcessWithoutNullStreams | undefined = undefined
|
|
6
|
+
|
|
7
|
+
export default async function launchViteServer({
|
|
8
|
+
port = 3000,
|
|
9
|
+
cmd = 'yarn client',
|
|
10
|
+
timeout = 5000,
|
|
11
|
+
}: { port?: number; cmd?: string; timeout?: number } = {}) {
|
|
12
|
+
if (serverProcess) return
|
|
13
|
+
|
|
14
|
+
if (process.env.DEBUG === '1') console.log('Starting server...')
|
|
15
|
+
const [_cmd, ...args] = cmd.split(' ')
|
|
16
|
+
|
|
17
|
+
serverProcess = spawn(_cmd, args, {
|
|
18
|
+
detached: true,
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
BROWSER: 'none',
|
|
22
|
+
VITE_PSYCHIC_ENV: 'test',
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
await waitForPort(port, timeout)
|
|
27
|
+
|
|
28
|
+
serverProcess.stdout.on('data', data => {
|
|
29
|
+
if (process.env.DEBUG === '1') console.log(`Server output: ${data}`)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
serverProcess.on('error', err => {
|
|
33
|
+
throw err
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
serverProcess.stdout.on('data', data => {
|
|
37
|
+
if (process.env.DEBUG === '1') console.log(`Server output: ${data}`)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
serverProcess.stderr.on('data', data => {
|
|
41
|
+
if (process.env.DEBUG === '1') console.error(`Server error: ${data}`)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
serverProcess.on('error', err => {
|
|
45
|
+
console.error(`Server process error: ${err as unknown as string}`)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
serverProcess.on('close', code => {
|
|
49
|
+
if (process.env.DEBUG === '1') console.log(`Server process exited with code ${code}`)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function stopViteServer() {
|
|
54
|
+
if (serverProcess?.pid) {
|
|
55
|
+
if (process.env.DEBUG === '1') console.log('Stopping server...')
|
|
56
|
+
// serverProcess.kill('SIGINT')
|
|
57
|
+
process.kill(-serverProcess.pid, 'SIGKILL')
|
|
58
|
+
serverProcess = undefined
|
|
59
|
+
|
|
60
|
+
if (process.env.DEBUG === '1') console.log('server stopped')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function isPortAvailable(port: number): Promise<boolean> {
|
|
65
|
+
return new Promise(resolve => {
|
|
66
|
+
const server = createServer()
|
|
67
|
+
.once('error', err => {
|
|
68
|
+
if ((err as any).code === 'EADDRINUSE') {
|
|
69
|
+
resolve(false)
|
|
70
|
+
} else {
|
|
71
|
+
resolve(true)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.once('listening', () => {
|
|
75
|
+
server.close()
|
|
76
|
+
resolve(true)
|
|
77
|
+
})
|
|
78
|
+
.listen(port, '127.0.0.1')
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function waitForPort(port: number, timeout: number = 5000) {
|
|
83
|
+
if (await isPortAvailable(port)) {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const startTime = Date.now()
|
|
88
|
+
|
|
89
|
+
async function recursiveWaitForPort() {
|
|
90
|
+
if (await isPortAvailable(port)) {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (Date.now() > startTime + timeout) {
|
|
95
|
+
stopViteServer()
|
|
96
|
+
throw new Error('waited too long for port: ' + port)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await sleep(50)
|
|
100
|
+
return await recursiveWaitForPort()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return await recursiveWaitForPort()
|
|
104
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
import evaluateWithRetryAndTimeout, {
|
|
3
|
+
ExpectToEvaluateOpts,
|
|
4
|
+
ExpectToEvaluateReturnType,
|
|
5
|
+
} from '../internal/evaluateWithRetryAndTimeout.js'
|
|
6
|
+
import toHaveSelector from '../matchers/toHaveSelector.js'
|
|
7
|
+
import toMatchTextContent from '../matchers/toMatchTextContent.js'
|
|
8
|
+
import toNotHaveSelector from '../matchers/toNotHaveSelector.js'
|
|
9
|
+
import toNotMatchTextContent from '../matchers/toNotMatchTextContent.js'
|
|
10
|
+
import toCheck from '../matchers/toCheck.js'
|
|
11
|
+
import toUncheck from '../matchers/toUncheck.js'
|
|
12
|
+
import toClick from '../matchers/toClick.js'
|
|
13
|
+
import toClickLink from '../matchers/toClickLink.js'
|
|
14
|
+
import toClickButton from '../matchers/toClickButton.js'
|
|
15
|
+
import toClickSelector from '../matchers/toClickSelector.js'
|
|
16
|
+
import toHavePath from '../matchers/toHavePath.js'
|
|
17
|
+
import toHaveUrl from '../matchers/toHaveUrl.js'
|
|
18
|
+
import toHaveLink from '../matchers/toHaveLink.js'
|
|
19
|
+
import toHaveChecked from '../matchers/toHaveChecked.js'
|
|
20
|
+
import toHaveUnchecked from '../matchers/toHaveUnchecked.js'
|
|
21
|
+
import toFill from '../matchers/toFill.js'
|
|
22
|
+
|
|
23
|
+
export default function providePuppeteerViteMatchers() {
|
|
24
|
+
;((global as any).expect as any).extend({
|
|
25
|
+
async toMatchTextContent(page: Page, text: string) {
|
|
26
|
+
return await toMatchTextContent(page, text)
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async toNotMatchTextContent(page: Page, text: string) {
|
|
30
|
+
return await toNotMatchTextContent(page, text)
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async toHaveSelector(page: Page, cssSelector: string) {
|
|
34
|
+
return await toHaveSelector(page, cssSelector)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async toNotHaveSelector(page: Page, cssSelector: string) {
|
|
38
|
+
return await toNotHaveSelector(page, cssSelector)
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async toCheck(page: Page, text: string) {
|
|
42
|
+
return await toCheck(page, text)
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async toClick(page: Page, text: string) {
|
|
46
|
+
return await toClick(page, text)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async toClickLink(page: Page, text: string) {
|
|
50
|
+
return await toClickLink(page, text)
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async toClickButton(page: Page, text: string) {
|
|
54
|
+
return await toClickButton(page, text)
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async toClickSelector(page: Page, cssSelector: string) {
|
|
58
|
+
return await toClickSelector(page, cssSelector)
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async toHavePath(page: Page, path: string) {
|
|
62
|
+
return await toHavePath(page, path)
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async toHaveUrl(page: Page, url: string) {
|
|
66
|
+
return await toHaveUrl(page, url)
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async toHaveChecked(page: Page, text: string) {
|
|
70
|
+
return await toHaveChecked(page, text)
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async toHaveUnchecked(page: Page, checked: string) {
|
|
74
|
+
return await toHaveUnchecked(page, checked)
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async toHaveLink(page: Page, text: string) {
|
|
78
|
+
return await toHaveLink(page, text)
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async toFill(page: Page, cssSelector: string, text: string) {
|
|
82
|
+
return await toFill(page, cssSelector, text)
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async toUncheck(page: Page, text: string) {
|
|
86
|
+
return await toUncheck(page, text)
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async toEvaluate(
|
|
90
|
+
argumentPassedToExpect: Page,
|
|
91
|
+
evaluationFn: (a: any) => ExpectToEvaluateReturnType | Promise<ExpectToEvaluateReturnType>,
|
|
92
|
+
opts: ExpectToEvaluateOpts
|
|
93
|
+
) {
|
|
94
|
+
return await evaluateWithRetryAndTimeout(argumentPassedToExpect, evaluationFn, opts)
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CustomMatcherResult {
|
|
100
|
+
pass: boolean
|
|
101
|
+
message: (actual?: unknown) => string
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
import launchPage from './launchPage.js'
|
|
3
|
+
|
|
4
|
+
export default async function visit(
|
|
5
|
+
path: string,
|
|
6
|
+
{ page, baseUrl = 'http://localhost:3000' }: { page?: Page; baseUrl?: string } = {}
|
|
7
|
+
) {
|
|
8
|
+
page ||= await launchPage()
|
|
9
|
+
await page.goto(`${baseUrl}/${path.replace(/^\//, '')}`)
|
|
10
|
+
return page
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import sleep from '../../shared/sleep.js'
|
|
2
|
+
|
|
3
|
+
export default async function evaluateWithRetryAndTimeout(
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
argumentPassedToExpect: any,
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
evaluationFn: (a: any) => ExpectToEvaluateReturnType | Promise<ExpectToEvaluateReturnType>,
|
|
8
|
+
opts: ExpectToEvaluateOpts
|
|
9
|
+
) {
|
|
10
|
+
const timeout = opts.timeout || 4000
|
|
11
|
+
const interval = opts.interval || 10
|
|
12
|
+
const startTime = Date.now()
|
|
13
|
+
|
|
14
|
+
async function intervalCb() {
|
|
15
|
+
const res = await evaluationFn(argumentPassedToExpect)
|
|
16
|
+
const expired = Date.now() >= startTime + timeout
|
|
17
|
+
|
|
18
|
+
if (res.pass) {
|
|
19
|
+
return {
|
|
20
|
+
message: () => (opts.successText ? opts.successText(res.actual) : 'Success'),
|
|
21
|
+
pass: true,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (expired) {
|
|
26
|
+
return {
|
|
27
|
+
message: () => opts.failureText(res.actual),
|
|
28
|
+
pass: false,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await sleep(interval)
|
|
33
|
+
return await intervalCb()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return await intervalCb()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
export type ExpectToEvaluateReturnType = { pass: boolean; actual: any }
|
|
41
|
+
|
|
42
|
+
export interface ExpectToEvaluateOpts {
|
|
43
|
+
timeout?: number
|
|
44
|
+
interval?: number
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
successText?: (received: any) => string
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
failureText: (received: any) => string
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
|
|
3
|
+
export default async function getAllTextContentFromPage(page: Page) {
|
|
4
|
+
// Evaluate and extract all text content on the page
|
|
5
|
+
const allText = await page.evaluate(() => {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
9
|
+
const elements = document.body.querySelectorAll('*')
|
|
10
|
+
|
|
11
|
+
const textContentArray: string[] = []
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
;(elements as any[]).forEach(element => {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
16
|
+
if (element.textContent.trim() !== '') {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
18
|
+
textContentArray.push(element.innerText.trim())
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return textContentArray.join(' ')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return allText
|
|
26
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import isPuppeteerPage from './isPuppeteerPage.js'
|
|
2
|
+
|
|
3
|
+
export default function requirePuppeteerPage(argumentPassedToExpect: any) {
|
|
4
|
+
if (!isPuppeteerPage(argumentPassedToExpect)) {
|
|
5
|
+
throw new Error('Must pass a puppeteer page to expect when calling toMatchTextContent')
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js'
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js'
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js'
|
|
5
|
+
|
|
6
|
+
export default async function toCheck(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
const checkbox = await page.$(`input[type="checkbox"][value="${expectedText}"]`)
|
|
13
|
+
if (!checkbox) return evaluationFailure(`A checkbox was not found with "${expectedText}"`)
|
|
14
|
+
|
|
15
|
+
const isChecked = await page.evaluate(checkbox => checkbox.checked, checkbox)
|
|
16
|
+
if (isChecked)
|
|
17
|
+
return evaluationFailure(
|
|
18
|
+
`A checkbox was found with "${expectedText}", but it is already checked`
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
await checkbox.click()
|
|
22
|
+
const isCheckedNow = await page.evaluate(checkbox => checkbox.checked, checkbox)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
pass: isCheckedNow,
|
|
26
|
+
actual: expectedText,
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
successText: r => `Expected page to have checkable checkbox with text: "${expectedText}"`,
|
|
31
|
+
failureText: r => `Expected page not to have checkable checkbox with text: "${expectedText}"`,
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Page, 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
|
+
|
|
6
|
+
export default async function toClick(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const el = await page.locator(`::-p-text(${expectedText})`).setTimeout(5000).wait()
|
|
14
|
+
await el.click()
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof TimeoutError) return evaluationFailure(expectedText)
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
pass: true,
|
|
22
|
+
actual: expectedText,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
successText: () => `Expected page to have clickable element with text: "${expectedText}"`,
|
|
27
|
+
failureText: () => `Expected page not to have clickable element with text: "${expectedText}"`,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Page, 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
|
+
|
|
6
|
+
export default async function toClickButton(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const el = await page.locator(`button ::-p-text(${expectedText})`).setTimeout(10).wait()
|
|
14
|
+
await el.click()
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof TimeoutError) return evaluationFailure(expectedText)
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
pass: true,
|
|
22
|
+
actual: expectedText,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
successText: () => `Expected page to have clickable button with text: "${expectedText}"`,
|
|
27
|
+
failureText: () => `Expected page not to have clickable button with text: "${expectedText}"`,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Page, 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
|
+
|
|
6
|
+
export default async function toClickLink(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const el = await page.locator(`a ::-p-text(${expectedText})`).setTimeout(5000).wait()
|
|
14
|
+
await el.click()
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof TimeoutError) return evaluationFailure(expectedText)
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
pass: true,
|
|
22
|
+
actual: expectedText,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
successText: () => `Expected page to have clickable link with text: "${expectedText}"`,
|
|
27
|
+
failureText: () => `Expected page not to have clickable link with text: "${expectedText}"`,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Page, 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
|
+
|
|
6
|
+
export default async function toClickSelector(page: Page, cssSelector: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const el = await page.locator(cssSelector).setTimeout(10).wait()
|
|
14
|
+
await el.click()
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof TimeoutError) return evaluationFailure(cssSelector)
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
pass: true,
|
|
22
|
+
actual: cssSelector,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
successText: () => `Expected page to have clickable selector with text: "${cssSelector}"`,
|
|
27
|
+
failureText: () => `Expected page not to have clickable selector with text: "${cssSelector}"`,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js'
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js'
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js'
|
|
5
|
+
|
|
6
|
+
export default async function toFill(page: Page, cssSelector: string, text: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await page.type(cssSelector, text)
|
|
14
|
+
} catch {
|
|
15
|
+
return evaluationFailure(text)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
pass: true,
|
|
20
|
+
actual: text,
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
successText: () => `Expected page to have clickable link with text: "${text}"`,
|
|
25
|
+
failureText: () => `Expected page not to have clickable link with text: "${text}"`,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Page } from 'puppeteer'
|
|
2
|
+
import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js'
|
|
3
|
+
import evaluationFailure from '../internal/evaluationFailure.js'
|
|
4
|
+
import requirePuppeteerPage from '../internal/requirePuppeteerPage.js'
|
|
5
|
+
|
|
6
|
+
export default async function toHaveChecked(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
const checkbox = await page.$(`input[type="checkbox"][value="${expectedText}"]`)
|
|
13
|
+
if (!checkbox) return evaluationFailure(`A checkbox was not found with "${expectedText}"`)
|
|
14
|
+
|
|
15
|
+
const isChecked = await page.evaluate(checkbox => checkbox.checked, checkbox)
|
|
16
|
+
return {
|
|
17
|
+
pass: isChecked,
|
|
18
|
+
actual: expectedText,
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
successText: r => `Expected page to have checked checkbox with text: "${expectedText}"`,
|
|
23
|
+
failureText: r => `Expected page not to have checked checkbox with text: "${expectedText}"`,
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Page, 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
|
+
|
|
6
|
+
export default async function toHaveLink(page: Page, expectedText: string) {
|
|
7
|
+
return await evaluateWithRetryAndTimeout(
|
|
8
|
+
page,
|
|
9
|
+
async () => {
|
|
10
|
+
requirePuppeteerPage(page)
|
|
11
|
+
|
|
12
|
+
let el: any = undefined
|
|
13
|
+
try {
|
|
14
|
+
el = await page.locator(`a ::-p-text(${expectedText})`).setTimeout(10).wait()
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err instanceof TimeoutError) return evaluationFailure(expectedText)
|
|
17
|
+
throw err
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
pass: !!el,
|
|
22
|
+
actual: expectedText,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
successText: () => `Expected page to have clickable link with text: "${expectedText}"`,
|
|
27
|
+
failureText: () => `Expected page not to have clickable link with text: "${expectedText}"`,
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
}
|