Skip to content

Commit f2bcd1c

Browse files
yusefnaporalidel
andauthored
feat: replace puppeteer with playwright for e2e tests (#1802)
* replace puppeteer with playwright * style(e2e): more friendly waitForText Co-authored-by: Marcin Rataj <[email protected]>
1 parent 84f4337 commit f2bcd1c

16 files changed

+4207
-2272
lines changed

package-lock.json

+4,056-2,171
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"devDependencies": {
105105
"@babel/core": "^7.11.1",
106106
"@olizilla/lol": "2.0.0",
107+
"@playwright/test": "^1.12.1",
107108
"@storybook/addon-a11y": "^5.3.19",
108109
"@storybook/addon-actions": "^5.3.19",
109110
"@storybook/addon-knobs": "^5.3.19",
@@ -133,10 +134,12 @@
133134
"ipfs": "^0.54.4",
134135
"ipfsd-ctl": "^7.2.0",
135136
"is-pull-stream": "0.0.0",
136-
"jest-puppeteer": "^4.4.0",
137+
"jest": "^26.6.3",
138+
"jest-playwright-preset": "^1.6.1",
139+
"jest-process-manager": "^0.3.1",
137140
"multihashing-async": "^1.0.0",
138141
"npm-run-all": "^4.1.5",
139-
"puppeteer": "^8.0.0",
142+
"playwright-chromium": "^1.12.1",
140143
"run-script-os": "^1.1.1",
141144
"shx": "^0.3.2",
142145
"typescript": "^4.1.3",

test/e2e/explore.test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll */
1+
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll, waitForText */
22

33
const fs = require('fs')
44

55
describe('Explore screen', () => {
66
beforeAll(async () => {
7-
await page.goto(webuiUrl + '#/explore', { waitUntil: 'networkidle0' })
7+
await page.goto(webuiUrl + '#/explore', { waitUntil: 'networkidle' })
88
})
99

1010
it('should have Project Apollo Archive as one of examples', async () => {
1111
await page.waitForSelector('a[href="#/explore/QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D"]')
12-
await expect(page).toMatch('Project Apollo Archive')
13-
await expect(page).toMatch('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')
12+
await waitForText('Project Apollo Archives')
13+
await waitForText('QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D')
1414
})
1515

1616
it('should open arbitrary CID', async () => {
@@ -21,11 +21,11 @@ describe('Explore screen', () => {
2121
await expect(result.cid.toString()).toStrictEqual(cid)
2222

2323
// open inspector
24-
await page.goto(webuiUrl + `#/explore/${cid}`, { waitUntil: 'networkidle0' })
24+
await page.goto(webuiUrl + `#/explore/${cid}`, { waitUntil: 'networkidle' })
2525
await page.waitForSelector(`a[href="#/explore/${cid}"]`)
2626
// expect node type
27-
await expect(page).toMatch('Raw Block')
27+
await waitForText('Raw Block')
2828
// expect cid details
29-
await expect(page).toMatch('base32 - cidv1 - raw - sha2-256~256~46532C71D1B730E168548410DDBB4186A2C3C0659E915B19D47F373EC6C5174A')
29+
await waitForText('base32 - cidv1 - raw - sha2-256~256~46532C71D1B730E168548410DDBB4186A2C3C0659E915B19D47F373EC6C5174A')
3030
})
3131
})

test/e2e/files.test.js

+22-19
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
1-
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll */
1+
/* global webuiUrl, ipfs, page, describe, it, beforeAll, waitForText */
22

33
const { fixtureData } = require('./fixtures')
44
const all = require('it-all')
55
const filesize = require('filesize')
66

77
describe('Files screen', () => {
88
beforeAll(async () => {
9-
await page.goto(webuiUrl + '#/files', { waitUntil: 'networkidle0' })
9+
await page.goto(webuiUrl + '#/files', { waitUntil: 'networkidle' })
1010
})
1111

1212
const button = 'button[id="import-button"]'
1313

1414
it('should have the active Add menu', async () => {
15-
await page.waitForSelector(button, { visible: true })
16-
await page.click(button)
17-
await page.waitForSelector('#add-file', { visible: true })
18-
await expect(page).toMatch('File')
19-
await expect(page).toMatch('Folder')
20-
await expect(page).toMatch('From IPFS')
21-
await expect(page).toMatch('New folder')
22-
await page.click(button)
15+
await page.waitForSelector(button, { state: 'visible' })
16+
await page.click(button, { force: true })
17+
await page.waitForSelector('#add-file', { state: 'visible' })
18+
await waitForText('File')
19+
await waitForText('Folder')
20+
await waitForText('From IPFS')
21+
await waitForText('New folder')
22+
await page.click(button, { force: true })
2323
})
2424

2525
it('should allow for a successful import of two files', async () => {
26-
await page.waitForSelector(button, { visible: true })
26+
await page.waitForSelector(button, { state: 'visible' })
2727
await page.click(button)
28-
await page.waitForSelector('#add-file', { visible: true })
28+
await page.waitForSelector('#add-file', { state: 'visible' })
2929

3030
const [fileChooser] = await Promise.all([
31-
page.waitForFileChooser(),
31+
page.waitForEvent('filechooser'),
3232
page.click('button[id="add-file"]') // menu button that triggers file selection
3333
])
3434

3535
// select a single static text file via fileChooser
3636
const file1 = fixtureData('file.txt')
3737
const file2 = fixtureData('file2.txt')
38-
await fileChooser.accept([file1.path, file2.path])
38+
await fileChooser.setFiles([file1.path, file2.path])
3939

4040
// expect file with matching filename to be added to the file list
4141
await page.waitForSelector('.File')
42-
await expect(page).toMatch('file.txt')
43-
await expect(page).toMatch('file2.txt')
42+
await waitForText('file.txt')
43+
await waitForText('file2.txt')
4444

4545
// expect valid CID to be present on the page
4646
const [result1, result2] = await all(ipfs.addAll([file1.data, file2.data]))
47-
await expect(page).toMatch(result1.cid.toString())
48-
await expect(page).toMatch(result2.cid.toString())
47+
await waitForText(result1.cid.toString())
48+
await waitForText(result2.cid.toString())
4949

5050
// expect human readable sizes in format from ./src/lib/files.js#humanSize
5151
// → this ensures metadata was correctly read for each item in the MFS
@@ -55,7 +55,10 @@ describe('Files screen', () => {
5555
round: b >= 1073741824 ? 1 : 0
5656
}) : '-')
5757
for await (const file of ipfs.files.ls('/')) {
58-
await expect(page).toMatch(human(file.size))
58+
// the text matcher used by waitForText is particular about whitespace. When the file size is rendered, it uses a `&nbsp;` element, which translates to unicode character 0xa0.
59+
// If we try to match a plain space, it will fail, so we replace space with `\u00a0` here.
60+
const expected = human(file.size).replace(' ', '\u00a0')
61+
await waitForText(expected)
5962
}
6063
})
6164
})

test/e2e/jest-playwright.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const debug = process.env.DEBUG === 'true'
2+
const ci = process.env.TRAVIS === 'true' || process.env.CI === 'true'
3+
4+
// TODO: figure out what options to use
5+
module.exports = {
6+
launchOptions: {
7+
headless: (!debug || ci) // show browser window when in debug mode
8+
}
9+
}

test/e2e/jest-puppeteer.config.js

-9
This file was deleted.

test/e2e/jest.config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
2+
process.env.JEST_PLAYWRIGHT_CONFIG = './jest-playwright.config.js'
3+
14
module.exports = {
2-
preset: 'jest-puppeteer',
5+
preset: 'jest-playwright-preset',
36
testRegex: './*\\.test\\.js$',
47
testEnvironment: './setup/test-environment.js',
8+
testTimeout: 30 * 1000,
59
globalSetup: './setup/global-init.js',
610
setupFilesAfterEnv: ['./setup/global-after-env.js'],
711
globalTeardown: './setup/global-teardown.js'

test/e2e/navigation.test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global webuiUrl, waitForTitle, page, describe, it, expect, beforeAll */
1+
/* global webuiUrl, waitForTitle, page, describe, it, beforeAll, waitForText */
22

33
const scrollLinkContainer = async () => {
44
const linkContainer = '[role="menubar"]'
@@ -12,29 +12,29 @@ const scrollLinkContainer = async () => {
1212

1313
describe('Navigation menu', () => {
1414
beforeAll(async () => {
15-
await page.goto(webuiUrl + '#/blank', { waitUntil: 'networkidle0' })
15+
await page.goto(webuiUrl + '#/blank', { waitUntil: 'networkidle' })
1616
})
1717

1818
it('should work for Status page', async () => {
1919
const link = 'a[href="#/"]'
2020
await page.waitForSelector(link)
21-
await expect(page).toMatch('Status')
21+
await waitForText('Status')
2222
await page.click(link)
2323
await waitForTitle('Status | IPFS')
2424
})
2525

2626
it('should work for Files page', async () => {
2727
const link = 'a[href="#/files"]'
2828
await page.waitForSelector(link)
29-
await expect(page).toMatch('Files')
29+
await waitForText('Files')
3030
await page.click(link)
3131
await waitForTitle('/ | Files | IPFS')
3232
})
3333

3434
it('should work for Explore page', async () => {
3535
const link = 'a[href="#/explore"]'
3636
await page.waitForSelector(link)
37-
await expect(page).toMatch('Explore')
37+
await waitForText('Explore')
3838
await scrollLinkContainer()
3939
await page.click(link)
4040
await waitForTitle('Explore | IPLD')
@@ -43,7 +43,7 @@ describe('Navigation menu', () => {
4343
it('should work for Peers page', async () => {
4444
const link = 'a[href="#/peers"]'
4545
await page.waitForSelector(link)
46-
await expect(page).toMatch('Peers')
46+
await waitForText('Peers')
4747
await scrollLinkContainer()
4848
await page.click(link)
4949
await waitForTitle('Peers | IPFS')
@@ -52,7 +52,7 @@ describe('Navigation menu', () => {
5252
it('should work for Settings page', async () => {
5353
const link = 'a[href="#/settings"]'
5454
await page.waitForSelector(link)
55-
await expect(page).toMatch('Settings')
55+
await waitForText('Settings')
5656
await scrollLinkContainer()
5757
await page.click(link)
5858
await waitForTitle('Settings | IPFS')

test/e2e/peers.test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global webuiUrl, ipfs page, describe, it, expect, beforeAll, afterAll */
1+
/* global webuiUrl, ipfs, page, describe, it, beforeAll, afterAll, waitForText */
22

33
const { createController } = require('ipfsd-ctl')
44

@@ -18,15 +18,15 @@ describe('Peers screen', () => {
1818
peeraddr = addresses.find((ma) => ma.toString().startsWith('/ip4/127.0.0.1')).toString()
1919
// connect to peer to have something in the peer table
2020
await ipfs.swarm.connect(peeraddr)
21-
await page.goto(webuiUrl + '#/peers', { waitUntil: 'networkidle0' })
21+
await page.goto(webuiUrl + '#/peers', { waitUntil: 'networkidle' })
2222
})
2323

2424
it('should have a clickable "Add connection" button', async () => {
2525
const addConnection = 'Add connection'
26-
await expect(page).toMatch(addConnection)
27-
await expect(page).toClick('button', { text: addConnection })
26+
await waitForText(addConnection)
27+
await page.click(`text=${addConnection}`)
2828
await page.waitForSelector('div[role="dialog"]')
29-
await expect(page).toMatch('Insert the peer address you want to connect to')
29+
await waitForText('Insert the peer address you want to connect to')
3030
})
3131

3232
it('should confirm connection after "Add connection" ', async () => {
@@ -36,11 +36,11 @@ describe('Peers screen', () => {
3636
await page.keyboard.type('\n')
3737
// expect connection confirmation
3838
await page.waitForSelector('.bg-green', { visible: true })
39-
await expect(page).toMatch('Successfully connected to the provided peer')
39+
await waitForText('Successfully connected to the provided peer')
4040
})
4141

4242
it('should have a peer from a "Local Network"', async () => {
43-
await expect(page).toMatch('Local Network')
43+
await waitForText('Local Network')
4444
})
4545

4646
afterAll(async () => {

test/e2e/remote-api.test.js

+29-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global ipfs, webuiUrl, page, describe, it, expect, beforeAll */
1+
/* global ipfs, webuiUrl, page, describe, it, expect, beforeAll, waitForText */
22

33
const { createController } = require('ipfsd-ctl')
44
const getPort = require('get-port')
@@ -105,11 +105,11 @@ const switchIpfsApiEndpointViaLocalStorage = async (endpoint) => {
105105
}
106106

107107
const switchIpfsApiEndpointViaSettings = async (endpoint) => {
108-
await expect(page).toClick('a[href="#/settings"]')
108+
await page.click('a[href="#/settings"]')
109109
const selector = 'input[id="api-address"]'
110-
await page.waitForSelector(selector, { visible: true })
111-
await expect(page).toFill(selector, endpoint)
112-
await page.type(selector, '\n')
110+
await expect(page).toHaveSelector(selector)
111+
await page.fill(selector, endpoint)
112+
await page.press(selector, 'Enter')
113113
await waitForIpfsApiEndpoint(endpoint)
114114
}
115115

@@ -118,7 +118,8 @@ const waitForIpfsApiEndpoint = async (endpoint) => {
118118
try {
119119
// unwrap port if JSON config is passed
120120
const json = JSON.parse(endpoint)
121-
endpoint = json.port || endpoint
121+
const uri = new URL(json.url)
122+
endpoint = uri.port || endpoint
122123
} catch (_) {}
123124
try {
124125
// unwrap port if inlined basic auth was passed
@@ -128,10 +129,11 @@ const waitForIpfsApiEndpoint = async (endpoint) => {
128129
endpoint = uri.port || endpoint
129130
}
130131
} catch (_) {}
131-
await page.waitForFunction(`localStorage.getItem('ipfsApi') && localStorage.getItem('ipfsApi').includes('${endpoint}')`)
132+
// await page.waitForFunction(`localStorage.getItem('ipfsApi') && localStorage.getItem('ipfsApi').includes('${endpoint}')`)
133+
await page.waitForFunction(endpoint => window.localStorage.getItem('ipfsApi') && window.localStorage.getItem('ipfsApi').includes(endpoint), endpoint)
132134
return
133135
}
134-
await page.waitForFunction('localStorage.getItem(\'ipfsApi\') === null')
136+
await page.waitForFunction(() => window.localStorage.getItem('ipfsApi') === null)
135137
}
136138

137139
const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {
@@ -140,6 +142,7 @@ const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {
140142
await expectHttpApiAddressOnStatusPage('Custom JSON configuration')
141143
// confirm webui is actually connected to expected node :^)
142144
await expectPeerIdOnStatusPage(ipfsd.api)
145+
143146
// (2) go to Settings and confirm API string includes expected JSON config
144147
const apiOptions = JSON.stringify({
145148
url: `http://127.0.0.1:${proxyPort}/`,
@@ -152,28 +155,31 @@ const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {
152155

153156
const expectPeerIdOnStatusPage = async (api) => {
154157
const { id } = await api.id()
155-
await expect(page).toMatch(id)
158+
await waitForText(id)
156159
}
157160

158161
const expectHttpApiAddressOnStatusPage = async (value) => {
159-
await expect(page).toClick('a[href="#/"]')
162+
await page.waitForSelector('a[href="#/"]')
163+
await page.click('a[href="#/"]')
160164
await page.reload() // instant addr update for faster CI
161-
await page.waitForSelector('summary', { visible: true })
162-
await expect(page).toClick('summary', { text: 'Advanced' })
163-
const apiAddressOnStatus = await page.waitForSelector('div[id="http-api-address"]', { visible: true })
164-
await expect(apiAddressOnStatus).toMatch(String(value))
165+
await page.waitForSelector('summary', { state: 'visible' })
166+
await page.click('summary')
167+
await page.waitForSelector('div[id="http-api-address"]', { state: 'visible' })
168+
await waitForText(String(value))
165169
}
166170

167171
const expectHttpApiAddressOnSettingsPage = async (value) => {
168-
await expect(page).toClick('a[href="#/settings"]')
169-
await page.waitForSelector('input[id="api-address"]', { visible: true })
172+
await expect(page).toHaveSelector('a[href="#/settings"]')
173+
await page.click('a[href="#/settings"]')
174+
await page.waitForSelector('input[id="api-address"]', { state: 'visible' })
170175
const apiAddrInput = await page.$('#api-address')
171176
const apiAddrValue = await page.evaluate(x => x.value, apiAddrInput)
172177
// if API address is defined as JSON, match objects
173178
try {
174179
const json = JSON.parse(apiAddrValue)
175180
const expectedJson = JSON.parse(value)
176-
return await expect(json).toMatchObject(expectedJson)
181+
await expect(json).toMatchObject(expectedJson)
182+
return
177183
} catch (_) {}
178184
// else, match strings (Multiaddr or URL)
179185
await expect(apiAddrValue).toMatch(String(value))
@@ -221,8 +227,12 @@ describe('API @ URL', () => {
221227
})
222228

223229
describe('API with CORS and Basic Auth', () => {
230+
afterEach(async () => {
231+
await switchIpfsApiEndpointViaLocalStorage(null)
232+
})
233+
224234
it('should work when localStorage[ipfsApi] is set to URL with inlined Basic Auth credentials', async () => {
225-
await switchIpfsApiEndpointViaLocalStorage(`http://${user}:${password}@127.0.0.1:${proxyPort}`)
235+
await switchIpfsApiEndpointViaLocalStorage(`http://${user}:${password}@127.0.0.1:${proxyPort}/`)
226236
await basicAuthConnectionConfirmation(user, password, proxyPort)
227237
})
228238

@@ -238,7 +248,7 @@ describe('API with CORS and Basic Auth', () => {
238248
})
239249

240250
it('should work when URL with inlined credentials are entered at the Settings page', async () => {
241-
const basicAuthApiAddr = `http://${user}:${password}@127.0.0.1:${proxyPort}`
251+
const basicAuthApiAddr = `http://${user}:${password}@127.0.0.1:${proxyPort}/`
242252
await switchIpfsApiEndpointViaSettings(basicAuthApiAddr)
243253
await basicAuthConnectionConfirmation(user, password, proxyPort)
244254
})

0 commit comments

Comments
 (0)