Skip to content

Commit

Permalink
Merge pull request #46 from gnosis/e2e-tests
Browse files Browse the repository at this point in the history
E2E tests
  • Loading branch information
jfschwarz authored Dec 22, 2022
2 parents b810e95 + 6791ae3 commit 2d1406b
Show file tree
Hide file tree
Showing 30 changed files with 4,593 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/api-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@main
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
Expand Down
19 changes: 8 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
name: Tests
name: CI

on: [push]

jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: yarn
working-directory: ./extension
- run: yarn run check
cache: "yarn"
cache-dependency-path: "**/yarn.lock"
- run: yarn install --frozen-lockfile
working-directory: ./extension
- run: yarn build
- name: Run static checks
run: yarn run check
working-directory: ./extension
37 changes: 37 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: End-to-end tests

on: [push]

jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: "yarn"
cache-dependency-path: "**/yarn.lock"
- name: Build extension
working-directory: ./extension
run: |
yarn install --frozen-lockfile
yarn build
- run: yarn install --frozen-lockfile
working-directory: ./e2e-tests
env:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true"
- name: Run end-to-end tests
uses: mujo-code/[email protected]
env:
CI: "true"
SEED_PHRASE: ${{ secrets.E2E_TESTS_SEED_PHRASE }}
with:
args: yarn --cwd ./e2e-tests start
- name: Upload failing test screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: failing-test-screenshots
path: e2e-tests/output/screenshots
retention-days: 7
8 changes: 4 additions & 4 deletions .github/workflows/landing-page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ jobs:
runs-on: ubuntu-latest
name: Deploy Landing Page
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install -g yarn
cache: "yarn"
cache-dependency-path: "**/yarn.lock"
- run: yarn install --frozen-lockfile
working-directory: ./landing-page
- run: yarn build
working-directory: ./landing-page

- name: Deploy to Github Pages
uses: JamesIves/[email protected]
with:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/prod-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ jobs:
runs-on: ubuntu-latest
name: Build Extension
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: "npm"
cache-dependency-path: "extension/yarn.lock"
- run: npm install -g yarn
cache: "yarn"
cache-dependency-path: "**/yarn.lock"

- name: Install deps and build
working-directory: ./extension
run: |
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ For retrieving the new iframe location, we then post a message to the injected s
When the simulator iframe opens any page, we inject the build/inject.js script as a node into the DOM of the Dapp.

The injected script then runs in the context of the Dapp and injects an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible API at `window.ethereum`.
The injected provider forwards all `request` calls to the parent extension page via `window.postMessage` where they are handled with on of our providers.
We currently offer two kinds of providers, the `WrappingProvider` for synchronously dispatching the transaction as a meta transaction of a Zodiac module function call, and `ForkProvider` for simulating the transaction in a local fork of the connected network and recording it for later batch transmission.
The injected provider forwards all `request` calls to the parent extension page via `window.postMessage` where they are recorded and executed in a fork of the connected network.

### Simulating transaction in a fork

When the provider we inject into the Dapp iframe receives a transaction request, we record it and simulate the transaction in a fork of the target network, impersonating the Avatar account.
When the provider we inject into the Dapp iframe receives a transaction request, we record it and simulate the transaction in a fork of the target network, impersonating the Safe.
That way the app can continue communicating with the fork network, so that a whole session of multiple transactions can be recorded before anything is signed and submitted to the real chain.

There are two options available for simulating transactions in a fork, [Tenderly](https://tenderly.co) and a [Ganache](https://trufflesuite.com/ganache/) EVM running locally in the browser.
Expand All @@ -95,13 +94,12 @@ TODO: The following is still true, but we should adjust the implementation now t
> Again we communicate via `window.postMessage`. That way we connect Ganache to the WalletConnect provider in the extension page so it can fork the active network.
> At the same time, we connect the Dapp injected provider to [`ForkProvider`](src/providers/ForkProvider.ts) in the host page, which forwards requests to the Ganache provider running in the ganache iframe.
### Wrapping of transactions
### Submitting transactions

A batch of recorded transaction can finally be submitted as a multi-send transaction.
Zodiac Pilot is currently geared to submitting transactions via a [Roles mod](https://github.com/gnosis/zodiac-modifier-roles), which means that the multi-send call needs to wrapped in a Roles mod's `execTransactionWithRole` call.
This is implemented in the [WrappingProvider](src/providers/WrappingProvider.ts).

In the future, we plan to make Zodiac Pilot more generally useful, meaning that users will be able to customize the exact way of transaction wrapping.
Zodiac Pilot can be configured to submit transactions directly to the Safe if the Pilot account is an owner of delegate, or to route the transaction through Zodiac mods.
This is implemented in [WrappingProvider](src/providers/WrappingProvider.ts).
It currently supports the [Roles](https://github.com/gnosis/zodiac-modifier-roles) and [Delay](https://github.com/gnosis/zodiac-modifier-delay) mods.

### Overview of providers

Expand Down
18 changes: 18 additions & 0 deletions e2e-tests/.cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "0.2",
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
"language": "en",
"words": [
"auryn",
"chainsafe",
"dappeteer",
"headful"
],
"flagWords": [],
"ignorePaths": [
"package.json",
"yarn.lock",
"node_modules/**",
"tsconfig.json"
]
}
1 change: 1 addition & 0 deletions e2e-tests/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SEED_PHRASE=food garment clinic together tiny spread ball favorite slush link blur undo
5 changes: 5 additions & 0 deletions e2e-tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.log
.DS_Store
.env
/node_modules
/output
5 changes: 5 additions & 0 deletions e2e-tests/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
7 changes: 7 additions & 0 deletions e2e-tests/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"streetsidesoftware.code-spell-checker"
]
}
13 changes: 13 additions & 0 deletions e2e-tests/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"cSpell.userWords": [], // only use words from .cspell.json
"cSpell.enabled": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"jest.jestCommandLine": "yarn start",
"jest.autoRun": {}
}
6 changes: 6 additions & 0 deletions e2e-tests/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
}
13 changes: 13 additions & 0 deletions e2e-tests/helpers/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Wallet } from 'ethers'

if (!process.env.SEED_PHRASE) {
throw new Error('SEED_PHRASE env variable is not set')
}

export const account = Wallet.fromMnemonic(process.env.SEED_PHRASE).address // 0xB07520a4494793461Cf8762246B7D8695548C22B

if (account !== '0xB07520a4494793461Cf8762246B7D8695548C22B') {
console.warn(
`The test Safe and module setup that some tests rely on is using the MetaMask account 0xB07520a4494793461Cf8762246B7D8695548C22B. Your seed phrase is different: ${account}`
)
}
79 changes: 79 additions & 0 deletions e2e-tests/helpers/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import puppeteer, { Browser, Page } from 'puppeteer'

import { promises } from 'fs'
import os from 'os'
import path from 'path'
import NodeEnvironment from 'jest-environment-node'
import { Dappeteer, getMetamaskWindow } from '@chainsafe/dappeteer'
import { screenshot } from './screenshot'

const DIR = path.join(os.tmpdir(), 'jest_dappeteer_global_setup')

interface GlobalAdditions {
browser: Browser
metamask: Dappeteer
}

class DappeteerEnvironment extends NodeEnvironment {
global: NodeEnvironment['global'] & GlobalAdditions

async setup() {
await super.setup()
// get the wsEndpoint
const wsEndpoint = await promises.readFile(
path.join(DIR, 'wsEndpoint'),
'utf8'
)
if (!wsEndpoint) throw new Error('wsEndpoint not found')

// connect to puppeteer
const browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
})

this.global.browser = browser
this.global.metamask = await getMetamaskWindow(browser)
}

// Take a screenshot of the page on failing tests
async handleTestEvent(event, state) {
if (event.name === 'test_fn_failure') {
console.log('Taking screenshot on failing test...')

const page = await this.getActivePilotPage()
if (page) {
await screenshot(page, state.currentlyRunningTest.name)
await page.close()
}
await screenshot(
this.global.metamask.page,
`${state.currentlyRunningTest.name}-metamask`
)
}
}

async getActivePilotPage(): Promise<Page | undefined> {
const pages = await this.global.browser.pages()
const pilotPages = pages.filter((page) =>
page.url().startsWith('https://pilot.gnosisguild.org')
)
let lastModifiedPilotPage: Page | undefined = undefined
let lastModifiedDate = 0
let l = pilotPages[0]
for (const pilotPage of pilotPages) {
const lastModified = new Date(
await pilotPage.evaluate(() => document.lastModified)
).getTime()
if (lastModified > lastModifiedDate) {
lastModifiedDate = lastModified
lastModifiedPilotPage = pilotPage
}
}
if (!lastModifiedPilotPage) {
console.warn('No active Pilot extension page found')
}
return lastModifiedPilotPage
}
}

module.exports = DappeteerEnvironment
Loading

0 comments on commit 2d1406b

Please sign in to comment.