Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: replace puppeteer with playwright for e2e tests #1802

Merged
merged 2 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6,227 changes: 4,056 additions & 2,171 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"devDependencies": {
"@babel/core": "^7.11.1",
"@olizilla/lol": "2.0.0",
"@playwright/test": "^1.12.1",
"@storybook/addon-a11y": "^5.3.19",
"@storybook/addon-actions": "^5.3.19",
"@storybook/addon-knobs": "^5.3.19",
Expand Down Expand Up @@ -133,10 +134,12 @@
"ipfs": "^0.54.4",
"ipfsd-ctl": "^7.2.0",
"is-pull-stream": "0.0.0",
"jest-puppeteer": "^4.4.0",
"jest": "^26.6.3",
"jest-playwright-preset": "^1.6.1",
"jest-process-manager": "^0.3.1",
"multihashing-async": "^1.0.0",
"npm-run-all": "^4.1.5",
"puppeteer": "^8.0.0",
"playwright-chromium": "^1.12.1",
"run-script-os": "^1.1.1",
"shx": "^0.3.2",
"typescript": "^4.1.3",
Expand Down
14 changes: 7 additions & 7 deletions test/e2e/explore.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll */
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll, waitForText */

const fs = require('fs')

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

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

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

// open inspector
await page.goto(webuiUrl + `#/explore/${cid}`, { waitUntil: 'networkidle0' })
await page.goto(webuiUrl + `#/explore/${cid}`, { waitUntil: 'networkidle' })
await page.waitForSelector(`a[href="#/explore/${cid}"]`)
// expect node type
await expect(page).toMatch('Raw Block')
await waitForText('Raw Block')
// expect cid details
await expect(page).toMatch('base32 - cidv1 - raw - sha2-256~256~46532C71D1B730E168548410DDBB4186A2C3C0659E915B19D47F373EC6C5174A')
await waitForText('base32 - cidv1 - raw - sha2-256~256~46532C71D1B730E168548410DDBB4186A2C3C0659E915B19D47F373EC6C5174A')
})
})
41 changes: 22 additions & 19 deletions test/e2e/files.test.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
/* global webuiUrl, ipfs, page, describe, it, expect, beforeAll */
/* global webuiUrl, ipfs, page, describe, it, beforeAll, waitForText */

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

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

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

it('should have the active Add menu', async () => {
await page.waitForSelector(button, { visible: true })
await page.click(button)
await page.waitForSelector('#add-file', { visible: true })
await expect(page).toMatch('File')
await expect(page).toMatch('Folder')
await expect(page).toMatch('From IPFS')
await expect(page).toMatch('New folder')
await page.click(button)
await page.waitForSelector(button, { state: 'visible' })
await page.click(button, { force: true })
await page.waitForSelector('#add-file', { state: 'visible' })
await waitForText('File')
await waitForText('Folder')
await waitForText('From IPFS')
await waitForText('New folder')
await page.click(button, { force: true })
})

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

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

// select a single static text file via fileChooser
const file1 = fixtureData('file.txt')
const file2 = fixtureData('file2.txt')
await fileChooser.accept([file1.path, file2.path])
await fileChooser.setFiles([file1.path, file2.path])

// expect file with matching filename to be added to the file list
await page.waitForSelector('.File')
await expect(page).toMatch('file.txt')
await expect(page).toMatch('file2.txt')
await waitForText('file.txt')
await waitForText('file2.txt')

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

// expect human readable sizes in format from ./src/lib/files.js#humanSize
// → this ensures metadata was correctly read for each item in the MFS
Expand All @@ -55,7 +55,10 @@ describe('Files screen', () => {
round: b >= 1073741824 ? 1 : 0
}) : '-')
for await (const file of ipfs.files.ls('/')) {
await expect(page).toMatch(human(file.size))
// the text matcher used by waitForText is particular about whitespace. When the file size is rendered, it uses a ` ` element, which translates to unicode character 0xa0.
// If we try to match a plain space, it will fail, so we replace space with `\u00a0` here.
const expected = human(file.size).replace(' ', '\u00a0')
await waitForText(expected)
}
})
})
9 changes: 9 additions & 0 deletions test/e2e/jest-playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const debug = process.env.DEBUG === 'true'
const ci = process.env.TRAVIS === 'true' || process.env.CI === 'true'

// TODO: figure out what options to use
module.exports = {
launchOptions: {
headless: (!debug || ci) // show browser window when in debug mode
}
}
9 changes: 0 additions & 9 deletions test/e2e/jest-puppeteer.config.js

This file was deleted.

6 changes: 5 additions & 1 deletion test/e2e/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@

process.env.JEST_PLAYWRIGHT_CONFIG = './jest-playwright.config.js'

module.exports = {
preset: 'jest-puppeteer',
preset: 'jest-playwright-preset',
testRegex: './*\\.test\\.js$',
testEnvironment: './setup/test-environment.js',
testTimeout: 30 * 1000,
globalSetup: './setup/global-init.js',
setupFilesAfterEnv: ['./setup/global-after-env.js'],
globalTeardown: './setup/global-teardown.js'
Expand Down
14 changes: 7 additions & 7 deletions test/e2e/navigation.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global webuiUrl, waitForTitle, page, describe, it, expect, beforeAll */
/* global webuiUrl, waitForTitle, page, describe, it, beforeAll, waitForText */

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

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

it('should work for Status page', async () => {
const link = 'a[href="#/"]'
await page.waitForSelector(link)
await expect(page).toMatch('Status')
await waitForText('Status')
await page.click(link)
await waitForTitle('Status | IPFS')
})

it('should work for Files page', async () => {
const link = 'a[href="#/files"]'
await page.waitForSelector(link)
await expect(page).toMatch('Files')
await waitForText('Files')
await page.click(link)
await waitForTitle('/ | Files | IPFS')
})

it('should work for Explore page', async () => {
const link = 'a[href="#/explore"]'
await page.waitForSelector(link)
await expect(page).toMatch('Explore')
await waitForText('Explore')
await scrollLinkContainer()
await page.click(link)
await waitForTitle('Explore | IPLD')
Expand All @@ -43,7 +43,7 @@ describe('Navigation menu', () => {
it('should work for Peers page', async () => {
const link = 'a[href="#/peers"]'
await page.waitForSelector(link)
await expect(page).toMatch('Peers')
await waitForText('Peers')
await scrollLinkContainer()
await page.click(link)
await waitForTitle('Peers | IPFS')
Expand All @@ -52,7 +52,7 @@ describe('Navigation menu', () => {
it('should work for Settings page', async () => {
const link = 'a[href="#/settings"]'
await page.waitForSelector(link)
await expect(page).toMatch('Settings')
await waitForText('Settings')
await scrollLinkContainer()
await page.click(link)
await waitForTitle('Settings | IPFS')
Expand Down
14 changes: 7 additions & 7 deletions test/e2e/peers.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global webuiUrl, ipfs page, describe, it, expect, beforeAll, afterAll */
/* global webuiUrl, ipfs, page, describe, it, beforeAll, afterAll, waitForText */

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

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

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

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

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

afterAll(async () => {
Expand Down
48 changes: 29 additions & 19 deletions test/e2e/remote-api.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global ipfs, webuiUrl, page, describe, it, expect, beforeAll */
/* global ipfs, webuiUrl, page, describe, it, expect, beforeAll, waitForText */

const { createController } = require('ipfsd-ctl')
const getPort = require('get-port')
Expand Down Expand Up @@ -105,11 +105,11 @@ const switchIpfsApiEndpointViaLocalStorage = async (endpoint) => {
}

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

Expand All @@ -118,7 +118,8 @@ const waitForIpfsApiEndpoint = async (endpoint) => {
try {
// unwrap port if JSON config is passed
const json = JSON.parse(endpoint)
endpoint = json.port || endpoint
const uri = new URL(json.url)
endpoint = uri.port || endpoint
} catch (_) {}
try {
// unwrap port if inlined basic auth was passed
Expand All @@ -128,10 +129,11 @@ const waitForIpfsApiEndpoint = async (endpoint) => {
endpoint = uri.port || endpoint
}
} catch (_) {}
await page.waitForFunction(`localStorage.getItem('ipfsApi') && localStorage.getItem('ipfsApi').includes('${endpoint}')`)
// await page.waitForFunction(`localStorage.getItem('ipfsApi') && localStorage.getItem('ipfsApi').includes('${endpoint}')`)
await page.waitForFunction(endpoint => window.localStorage.getItem('ipfsApi') && window.localStorage.getItem('ipfsApi').includes(endpoint), endpoint)
return
}
await page.waitForFunction('localStorage.getItem(\'ipfsApi\') === null')
await page.waitForFunction(() => window.localStorage.getItem('ipfsApi') === null)
}

const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {
Expand All @@ -140,6 +142,7 @@ const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {
await expectHttpApiAddressOnStatusPage('Custom JSON configuration')
// confirm webui is actually connected to expected node :^)
await expectPeerIdOnStatusPage(ipfsd.api)

// (2) go to Settings and confirm API string includes expected JSON config
const apiOptions = JSON.stringify({
url: `http://127.0.0.1:${proxyPort}/`,
Expand All @@ -152,28 +155,31 @@ const basicAuthConnectionConfirmation = async (user, password, proxyPort) => {

const expectPeerIdOnStatusPage = async (api) => {
const { id } = await api.id()
await expect(page).toMatch(id)
await waitForText(id)
}

const expectHttpApiAddressOnStatusPage = async (value) => {
await expect(page).toClick('a[href="#/"]')
await page.waitForSelector('a[href="#/"]')
await page.click('a[href="#/"]')
await page.reload() // instant addr update for faster CI
await page.waitForSelector('summary', { visible: true })
await expect(page).toClick('summary', { text: 'Advanced' })
const apiAddressOnStatus = await page.waitForSelector('div[id="http-api-address"]', { visible: true })
await expect(apiAddressOnStatus).toMatch(String(value))
await page.waitForSelector('summary', { state: 'visible' })
await page.click('summary')
await page.waitForSelector('div[id="http-api-address"]', { state: 'visible' })
await waitForText(String(value))
}

const expectHttpApiAddressOnSettingsPage = async (value) => {
await expect(page).toClick('a[href="#/settings"]')
await page.waitForSelector('input[id="api-address"]', { visible: true })
await expect(page).toHaveSelector('a[href="#/settings"]')
await page.click('a[href="#/settings"]')
await page.waitForSelector('input[id="api-address"]', { state: 'visible' })
const apiAddrInput = await page.$('#api-address')
const apiAddrValue = await page.evaluate(x => x.value, apiAddrInput)
// if API address is defined as JSON, match objects
try {
const json = JSON.parse(apiAddrValue)
const expectedJson = JSON.parse(value)
return await expect(json).toMatchObject(expectedJson)
await expect(json).toMatchObject(expectedJson)
return
} catch (_) {}
// else, match strings (Multiaddr or URL)
await expect(apiAddrValue).toMatch(String(value))
Expand Down Expand Up @@ -221,8 +227,12 @@ describe('API @ URL', () => {
})

describe('API with CORS and Basic Auth', () => {
afterEach(async () => {
await switchIpfsApiEndpointViaLocalStorage(null)
})

it('should work when localStorage[ipfsApi] is set to URL with inlined Basic Auth credentials', async () => {
await switchIpfsApiEndpointViaLocalStorage(`http://${user}:${password}@127.0.0.1:${proxyPort}`)
await switchIpfsApiEndpointViaLocalStorage(`http://${user}:${password}@127.0.0.1:${proxyPort}/`)
await basicAuthConnectionConfirmation(user, password, proxyPort)
})

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

it('should work when URL with inlined credentials are entered at the Settings page', async () => {
const basicAuthApiAddr = `http://${user}:${password}@127.0.0.1:${proxyPort}`
const basicAuthApiAddr = `http://${user}:${password}@127.0.0.1:${proxyPort}/`
await switchIpfsApiEndpointViaSettings(basicAuthApiAddr)
await basicAuthConnectionConfirmation(user, password, proxyPort)
})
Expand Down