Skip to content

Commit

Permalink
feat: add E2E tests for chromium and firefox
Browse files Browse the repository at this point in the history
  • Loading branch information
galargh committed Dec 19, 2022
1 parent aa6cd89 commit bf8646e
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 4 deletions.
9 changes: 6 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ ARG GROUP_ID
RUN curl -s https://ipfs.io/ipfs/QmbukYcmtyU6ZEKt6fepnvrTNa9F6VqsUPMUgNxQjEmphH > /usr/local/bin/jq && chmod +x /usr/local/bin/jq

RUN mkdir -p /home/node/app
WORKDIR /home/node/app

RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
userdel -f node && \
if getent group node ; then groupdel node; fi && \
groupadd -g ${GROUP_ID} node && \
useradd -l -u ${USER_ID} -g node node && \
chown -fhR ${USER_ID}:${GROUP_ID} /home/node; fi
RUN chown node:node /home/node/app

COPY --chown=${USER_ID}:${GROUP_ID} . /home/node/app
WORKDIR /home/node/app

COPY --chown=node:node ./package.json ./package-lock.json /home/node/app/

USER node

RUN npm run ci:install

COPY --chown=node:node . /home/node/app

ENV PATH="/home/node/app/node_modules/.bin:${PATH}"
5 changes: 5 additions & 0 deletions ci/access-control-allow-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
set -ex

ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["*"]'
11 changes: 11 additions & 0 deletions ci/download-release-artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
set -ex

IPFS_COMPANION_VERSION=${IPFS_COMPANION_VERSION:-$(jq -r '.version' ./add-on/manifest.common.json)}

id="$(curl --retry 5 --no-progress-meter "https://api.github.com/repos/ipfs/ipfs-companion/releases/tags/v$IPFS_COMPANION_VERSION" | jq '.id')"
assets="$(curl --retry 5 --no-progress-meter --location "https://api.github.com/repos/ipfs/ipfs-companion/releases/$id/assets" | jq -r '.[].name')"

for asset in $assets; do
curl --retry 5 --no-progress-meter --location --output "build/$asset" "https://github.com/ipfs/ipfs-companion/releases/download/v$IPFS_COMPANION_VERSION/$asset"
done
39 changes: 39 additions & 0 deletions docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
version: "3.9"
services:
firefox:
image: selenium/standalone-firefox:${FIREFOX_VERSION:-latest}
shm_size: 2g
ports:
- 4444
- 7900
chromium:
# WARN: `standalone-chrome` does NOT work on ARM-based machines;
# see https://github.com/SeleniumHQ/docker-selenium#experimental-mult-arch-aarch64armhfamd64-images;
# try using `seleniarm/standalone-chromium` instead
# export CHROMIUM_IMAGE=seleniarm/standalone-chromium
image: ${CHROMIUM_IMAGE:-selenium/standalone-chrome}:${CHROMIUM_VERSION:-latest}
shm_size: 2g
ports:
- 4444
- 7900
kubo:
image: ipfs/kubo:${KUBO_VERSION:-latest}
ports:
- 4001
- 5001
- 8080
volumes:
- ./ci/access-control-allow-all.sh:/container-init.d/001-access-control-allow-all.sh
e2e:
build:
dockerfile: ./Dockerfile
environment:
- SELENIUM_REMOTE_CHROMIUM_URL=http://chromium:4444
- SELENIUM_REMOTE_FIREFOX_URL=http://firefox:4444
- IPFS_API_URL=http://kubo:5001
- CUSTOM_GATEWAY_URL=http://kubo:8080
- TEST_E2E=1
- TEST_HEADLESS=${TEST_HEADLESS}
- IPFS_COMPANION_VERSION=${IPFS_COMPANION_VERSION}
volumes:
- ./build:/home/node/app/build
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"watch:js": "run-p watch:js:*",
"watch:js:webpack": "webpack --watch --mode development --devtool inline-source-map --config ./webpack.config.js",
"test": "run-s test:*",
"test:e2e": "mocha --timeout 300000 --exit --require ignore-styles \"test/e2e/**/*.test.js\"",
"test:functional": "c8 mocha --timeout 5000 --exit --require ignore-styles \"test/functional/**/*.test.js\"",
"lint": "run-s lint:*",
"lint:standard": "standard -v \"*.js\" \"add-on/src/**/*.js\" \"test/**/*.js\" \"scripts/**/*.js\"",
Expand All @@ -59,7 +60,11 @@
"beta-build": "docker rmi -f ipfs-companion-beta-build && docker build -t ipfs-companion-beta-build --build-arg USER_ID=$(id -u ${USER}) --build-arg GROUP_ID=$(id -g ${USER}) . && mkdir -p build && docker run --rm -it --net=host -e RELEASE_CHANNEL=beta -v $(pwd)/build:/home/node/app/build ipfs-companion-beta-build npm run ci:build",
"release-build": "docker rmi -f ipfs-companion-release-build && docker build -t ipfs-companion-release-build --build-arg USER_ID=$(id -u ${USER}) --build-arg GROUP_ID=$(id -g ${USER}) . && mkdir -p build && docker run --rm -it --net=host -e RELEASE_CHANNEL=stable -v $(pwd)/build:/home/node/app/build ipfs-companion-release-build npm run ci:build",
"dev-build": "npm ci && npm run build",
"yarn-build": "npm run dev-build"
"yarn-build": "npm run dev-build",
"compose:e2e:build": "docker compose --file docker-compose.e2e.yml build",
"compose:e2e:up": "docker compose --file docker-compose.e2e.yml up --remove-orphans --detach kubo chromium firefox",
"compose:e2e:test": "docker compose --file docker-compose.e2e.yml run e2e npm run test:e2e",
"compose:e2e:stop": "docker compose --file docker-compose.e2e.yml stop"
},
"private": true,
"preferGlobal": false,
Expand Down Expand Up @@ -101,6 +106,7 @@
"path": "0.12.7",
"raw-loader": "4.0.2",
"request-progress": "3.0.0",
"selenium-webdriver": "^4.7.1",
"shx": "0.3.4",
"sinon": "13.0.1",
"sinon-chrome": "3.0.1",
Expand Down
155 changes: 155 additions & 0 deletions test/e2e/ipfs-companion.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Builder, By, Key } from 'selenium-webdriver'
import { describe, it, before } from 'mocha'
import { expect } from 'chai'
import fs from 'fs'
import chrome from 'selenium-webdriver/chrome.js'
import firefox from 'selenium-webdriver/firefox.js'

async function delay (ms) {
return new Promise(res => setTimeout(res, ms), _ => {})
}

function getVersion () {
return process.env.IPFS_COMPANION_VERSION || JSON.parse(fs.readFileSync('add-on/manifest.common.json')).version
}

async function openChromiumBrowser () {
console.info('Opening Chromium browser')
const extension = `build/ipfs_companion-${getVersion()}_chromium.zip`
console.info(`Checking if ${extension} exists`)
expect(fs.existsSync(extension)).to.be.true // eslint-disable-line no-unused-expressions
const options = new chrome.Options()
options.addExtensions(extension)
options.addArguments('--lang=en-GB,en-US')
if (process.env.TEST_HEADLESS === '1') {
options.addArguments('--headless=chrome')
}
const builder = new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
if (process.env.SELENIUM_REMOTE_CHROMIUM_URL !== undefined) {
console.info(`Using remote webdriver: ${process.env.SELENIUM_REMOTE_CHROMIUM_URL}`)
builder.usingServer(process.env.SELENIUM_REMOTE_CHROMIUM_URL)
}
console.info('Starting Chromium')
const browser = await builder.build()
await delay(5000) // waiting for the browser/extension to load
console.info('Chromium is ready')
return browser
}

async function openFirefoxBrowser () {
console.info('Opening Firefox browser')
const extension = `build/ipfs_companion-${getVersion()}_firefox.zip`
console.info(`Checking if ${extension} exists`)
expect(fs.existsSync(extension)).to.be.true // eslint-disable-line no-unused-expressions
const options = new firefox.Options()
options.setPreference('intl.accept_languages', 'en-gb,en-us')
options.setPreference('intl.locale.requested', 'en-GB,en-US')
if (process.env.TEST_HEADLESS === '1') {
options.addArguments('--headless')
}
const builder = new Builder()
.forBrowser('firefox')
.setFirefoxOptions(options)
if (process.env.SELENIUM_REMOTE_FIREFOX_URL !== undefined) {
console.info(`Using remote webdriver: ${process.env.SELENIUM_REMOTE_FIREFOX_URL}`)
builder.usingServer(process.env.SELENIUM_REMOTE_FIREFOX_URL)
}
console.info('Starting Firefox')
const browser = builder.build()
console.info('Installing the extension')
await browser.installAddon(extension, true)
await delay(5000) // waiting for the browser/extension to load
console.info('Firefox is ready')
return browser
}

const ExtensionURLRegex = /^(moz|chrome)-extension:\/\/[^\/]+/ // eslint-disable-line no-useless-escape
async function findExtensionUrl (browser) {
console.info('Looking for an open extension tab')
const handles = await browser.getAllWindowHandles()
console.info(`Found ${handles.length} candidates`)

for (const handle of handles) {
console.info('Switching tabs')
await browser.switchTo().window(handle)
const url = await browser.getCurrentUrl()
console.info(`The current URL is: ${url}`)
const extensionURL = ExtensionURLRegex.exec(url)?.at(0)
if (extensionURL !== undefined) {
console.info(`Found the extension URL: ${extensionURL}`)
return extensionURL
}
}
console.error('No extension URL found')
}

async function updateExtensionSettings (browser, url, id, value) {
console.info('Updating extension setting')
console.info(`Going to: ${url}/dist/options/options.html`)
await browser.get(`${url}/dist/options/options.html`)
console.info(`Looking for an element: ${id}`)
let element = browser.findElement(By.id(id))
console.info(`Setting new value to: ${value}`)
await element.sendKeys('')
await delay(1000) // waiting for focus to be acquired
await element.clear()
await delay(1000) // waiting for input to be cleared
await element.sendKeys(value)
await element.sendKeys(Key.TAB)
await delay(5000) // waiting for the setting to be applied
console.info('Checking if the update worked')
element = browser.findElement(By.id(id))
const v = await element.getAttribute('value')
expect(v).to.equal(value)
console.info('The setting update is complete')
}

async function getNumberOfConnectedPeers (browser, url) {
console.info('Checking the number of connected peers')
console.info(`Going to: ${url}/dist/landing-pages/welcome/index.html`)
await browser.get(`${url}/dist/landing-pages/welcome/index.html`)
await delay(5000) // waiting for the connection number to appear
const html = await browser.getPageSource()
console.debug(html)
console.info('Looking for an element with text: \'Your node is connected to ...\'')
const p = browser.findElement(By.xpath("//p[text()='Your node is connected to ']"))
const span = p.findElement(By.css('span'))
const peers = await span.getText()
console.info(`There are ${peers} connected peers`)
return parseInt(peers)
}

async function runTest (browser) {
const url = await findExtensionUrl(browser)
expect(url).not.to.be.undefined // eslint-disable-line no-unused-expressions
await updateExtensionSettings(browser, url, 'ipfsApiUrl', process.env.IPFS_API_URL || 'http://127.0.0.1:5001')
await updateExtensionSettings(browser, url, 'customGatewayUrl', process.env.CUSTOM_GATEWAY_URL || 'http://localhost:8080')
const peers = await getNumberOfConnectedPeers(browser, url)
expect(peers).not.to.equal(0)
}

describe('ipfs-companion', function () {
before(() => {
if (process.env.TEST_E2E !== '1') {
this.skip()
}
})
it('should be able to discover peers in Chromium', async function () {
const browser = await openChromiumBrowser()
try {
await runTest(browser)
} finally {
await browser.quit()
}
})
it('should be able to discover peers in Firefox', async function () {
const browser = await openFirefoxBrowser()
try {
await runTest(browser)
} finally {
await browser.quit()
}
})
})

0 comments on commit bf8646e

Please sign in to comment.