-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add test suite for check the Safe Apps liveness (#447)
* Add liveness test for safe apps * Use chrome
- Loading branch information
Showing
11 changed files
with
762 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
name: Test Safe Apps | ||
on: | ||
workflow_dispatch: | ||
inputs: | ||
baseUrl: | ||
description: 'Safe Web URL' | ||
required: true | ||
default: 'http://gnosis-safe.io/app' | ||
networkPrefix: | ||
description: 'Address prefix (eth,rin...)' | ||
required: true | ||
default: 'eth' | ||
safeAddress: | ||
description: 'Safe address' | ||
required: true | ||
default: '0xc322cde085A4C6EFc865E77eDDdF39c31262Fc70' | ||
configServiceBaseUrl: | ||
description: 'Config service base URL' | ||
required: true | ||
default: 'https://safe-client.gnosis.io' | ||
schedule: | ||
# At 9:00 on every day-of-week from Monday through Friday | ||
- cron: '0 9 * * 1-5' | ||
|
||
jobs: | ||
e2e: | ||
runs-on: ubuntu-20.04 | ||
# let's make sure our tests pass on Chrome browser | ||
name: E2E on Chrome | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: cypress-io/github-action@v2 | ||
with: | ||
browser: chrome | ||
spec: cypress/integration/safe-apps-check.spec.js | ||
env: | ||
CI: 'true' | ||
CYPRESS_BASE_URL: ${{ github.event.inputs.baseUrl || 'http://gnosis-safe.io/app' }} | ||
CYPRESS_NETWORK_PREFIX: ${{ github.event.inputs.networkPrefix || 'eth' }} | ||
CYPRESS_TESTING_SAFE_ADDRESS: ${{ github.event.inputs.safeAddress || '0xc322cde085A4C6EFc865E77eDDdF39c31262Fc70' }} | ||
CYPRESS_CONFIG_SERVICE_BASE_URL: ${{ github.event.inputs.configServiceBaseUrl || 'https://safe-client.gnosis.io' }} | ||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} | ||
continue-on-error: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,7 @@ yarn-error.log* | |
|
||
# vscode folders | ||
.vscode | ||
|
||
cypress/reports | ||
cypress/videos | ||
cypress/screenshots |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"chromeWebSecurity": false, | ||
"video": false, | ||
"retries": { | ||
"runMode": 2, | ||
"openMode": 0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
const safeAppsList = Cypress.env('SAFE_APPS_LIST') || [] | ||
|
||
describe('Safe Apps List', () => { | ||
before(() => { | ||
expect(safeAppsList).to.be.an('array').and.to.have.length.greaterThan(0) | ||
}) | ||
|
||
safeAppsList.forEach(safeApp => { | ||
it(safeApp.name, () => { | ||
cy.visit( | ||
`${Cypress.env('BASE_URL')}/${Cypress.env('NETWORK_PREFIX')}:${Cypress.env( | ||
'TESTING_SAFE_ADDRESS', | ||
)}/apps?appUrl=${safeApp.url}`, | ||
) | ||
const iframeSelector = `iframe[id="iframe-${safeApp.url}"]` | ||
|
||
cy.findByText('Accept all').click({ force: true }) | ||
cy.findByText('Confirm').click({ force: true }) | ||
cy.frameLoaded(iframeSelector) | ||
cy.iframe(iframeSelector).get('#root,#app,.app,main,#__next,app-root,#___gatsby') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
const axios = require('axios') | ||
|
||
export const sendSlackMessage = async results => { | ||
if (results) { | ||
try { | ||
const url = process.env.SLACK_WEBHOOK_URL | ||
if (!url) { | ||
return | ||
} | ||
|
||
await axios.post(process.env.SLACK_WEBHOOK_URL, buildSlackMessage(results)) | ||
} catch (error) { | ||
console.error(error) | ||
} | ||
} | ||
} | ||
|
||
const buildSlackMessage = results => { | ||
const failedTests = results.runs[0].tests | ||
.filter(test => test.state === 'failed') | ||
.map(test => test.title[1]) | ||
|
||
const title = { | ||
type: 'section', | ||
text: { | ||
type: 'mrkdwn', | ||
text: '*Safe Apps liveness tests*', | ||
}, | ||
} | ||
|
||
const executionEnvironment = { | ||
type: 'section', | ||
fields: [ | ||
{ | ||
type: 'mrkdwn', | ||
text: `*Domain:*\n${process.env.CYPRESS_BASE_URL}`, | ||
}, | ||
{ | ||
type: 'mrkdwn', | ||
text: `*Network:*\n${process.env.CYPRESS_NETWORK_PREFIX}`, | ||
}, | ||
{ | ||
type: 'mrkdwn', | ||
text: `*Safe Address:*\n${process.env.CYPRESS_TESTING_SAFE_ADDRESS}`, | ||
}, | ||
{ | ||
type: 'mrkdwn', | ||
text: `*Config Service:*\n${process.env.CYPRESS_CONFIG_SERVICE_BASE_URL}`, | ||
}, | ||
], | ||
} | ||
|
||
const safeUrl = { | ||
type: 'section', | ||
text: { | ||
type: 'mrkdwn', | ||
text: `*Safe URL:*\n${process.env.CYPRESS_BASE_URL}/${process.env.CYPRESS_NETWORK_PREFIX}:${process.env.CYPRESS_TESTING_SAFE_ADDRESS}/apps`, | ||
}, | ||
} | ||
|
||
const executionResult = { | ||
type: 'section', | ||
text: { | ||
type: 'mrkdwn', | ||
text: `*Execution results:*\n${results.totalPassed} out of ${results.totalTests}, passed`, | ||
}, | ||
} | ||
|
||
const failingApps = { | ||
type: 'section', | ||
text: { | ||
type: 'mrkdwn', | ||
text: `*Failing Apps:* _${failedTests.toString()}_`, | ||
}, | ||
} | ||
|
||
const action = { | ||
type: 'section', | ||
text: { | ||
type: 'mrkdwn', | ||
text: 'Want to take a look to the execution ?', | ||
}, | ||
accessory: { | ||
type: 'button', | ||
text: { | ||
type: 'plain_text', | ||
text: 'Take me there !', | ||
emoji: true, | ||
}, | ||
value: 'click_me_123', | ||
url: 'https://github.com/safe-global/safe-react-apps/actions/workflows/safe-apps-check.yml', | ||
action_id: 'button-action', | ||
}, | ||
} | ||
|
||
const blocks = [title, executionEnvironment, safeUrl, executionResult] | ||
|
||
if (failedTests.length > 0) { | ||
blocks.push(failingApps) | ||
} | ||
blocks.push(action) | ||
|
||
return { | ||
blocks, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
const axios = require('axios') | ||
const { sendSlackMessage } = require('../lib/slack') | ||
|
||
require('dotenv').config() | ||
|
||
module.exports = async (on, config) => { | ||
on('after:run', sendSlackMessage) | ||
|
||
let safeAppsList | ||
|
||
try { | ||
safeAppsList = await axios.get( | ||
`${ | ||
process.env.CYPRESS_CONFIG_SERVICE_BASE_URL | ||
}/v1/chains/1/safe-apps?client_url=${encodeURIComponent(process.env.BASE_URL)}`, | ||
) | ||
} catch (e) { | ||
console.log('Unable to fetch the default list: ', e) | ||
} | ||
|
||
config.env.BASE_URL = process.env.CYPRESS_BASE_URL | ||
config.env.SAFE_APPS_LIST = safeAppsList.data | ||
config.env.NETWORK_PREFIX = process.env.CYPRESS_NETWORK_PREFIX | ||
config.env.TESTING_SAFE_ADDRESS = process.env.CYPRESS_TESTING_SAFE_ADDRESS | ||
|
||
return config | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
const DEFAULT_OPTS = { | ||
log: true, | ||
timeout: 30000, | ||
} | ||
|
||
const DEFAULT_IFRAME_SELECTOR = 'iframe' | ||
|
||
function sleep(timeout) { | ||
return new Promise(resolve => setTimeout(resolve, timeout)) | ||
} | ||
|
||
// This command checks that an iframe has loaded onto the page | ||
// - This will verify that the iframe is loaded to any page other than 'about:blank' | ||
// cy.frameLoaded() | ||
|
||
// - This will verify that the iframe is loaded to any url containing the given path part | ||
// cy.frameLoaded({ url: 'https://google.com' }) | ||
// cy.frameLoaded({ url: '/join' }) | ||
// cy.frameLoaded({ url: '?some=query' }) | ||
// cy.frameLoaded({ url: '#/hash/path' }) | ||
|
||
// - You can also give it a selector to check that a specific iframe has loaded | ||
// cy.frameLoaded('#my-frame') | ||
// cy.frameLoaded('#my-frame', { url: '/join' }) | ||
Cypress.Commands.add('frameLoaded', (selector, opts) => { | ||
if (selector === undefined) { | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} else if (typeof selector === 'object') { | ||
opts = selector | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} | ||
|
||
const fullOpts = { | ||
...DEFAULT_OPTS, | ||
...opts, | ||
} | ||
const log = fullOpts.log | ||
? Cypress.log({ | ||
name: 'frame loaded', | ||
displayName: 'frame loaded', | ||
message: [selector], | ||
}).snapshot() | ||
: null | ||
return cy.get(selector, { log: false }).then({ timeout: fullOpts.timeout }, async $frame => { | ||
log?.set('$el', $frame) | ||
if ($frame.length !== 1) { | ||
throw new Error( | ||
`cypress-iframe commands can only be applied to exactly one iframe at a time. Instead found ${$frame.length}`, | ||
) | ||
} | ||
|
||
const contentWindow = $frame.prop('contentWindow') | ||
const hasNavigated = fullOpts.url | ||
? () => | ||
typeof fullOpts.url === 'string' | ||
? contentWindow.location.toString().includes(fullOpts.url) | ||
: fullOpts.url?.test(contentWindow.location.toString()) | ||
: () => contentWindow.location.toString() !== 'about:blank' | ||
|
||
while (!hasNavigated()) { | ||
await sleep(100) | ||
} | ||
|
||
if (contentWindow.document.readyState === 'complete') { | ||
return $frame | ||
} | ||
|
||
const loadLog = Cypress.log({ | ||
name: 'Frame Load', | ||
message: [contentWindow.location.toString()], | ||
event: true, | ||
}).snapshot() | ||
await new Promise(resolve => { | ||
Cypress.$(contentWindow).on('load', resolve) | ||
}) | ||
loadLog.end() | ||
log?.finish() | ||
return $frame | ||
}) | ||
}) | ||
|
||
// This will cause subsequent commands to be executed inside of the given iframe | ||
// - This will verify that the iframe is loaded to any page other than 'about:blank' | ||
// cy.iframe().find('.some-button').should('be.visible').click() | ||
// cy.iframe().contains('Some hidden element').should('not.be.visible') | ||
// cy.find('#outside-iframe').click() // this will be executed outside the iframe | ||
|
||
// - You can also give it a selector to find elements inside of a specific iframe | ||
// cy.iframe('#my-frame').find('.some-button').should('be.visible').click() | ||
// cy.iframe('#my-second-frame').contains('Some hidden element').should('not.be.visible') | ||
Cypress.Commands.add('iframe', (selector, opts) => { | ||
if (selector === undefined) { | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} else if (typeof selector === 'object') { | ||
opts = selector | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} | ||
|
||
const fullOpts = { | ||
...DEFAULT_OPTS, | ||
...opts, | ||
} | ||
const log = fullOpts.log | ||
? Cypress.log({ | ||
name: 'iframe', | ||
displayName: 'iframe', | ||
message: [selector], | ||
}).snapshot() | ||
: null | ||
return cy.frameLoaded(selector, { ...fullOpts, log: false }).then($frame => { | ||
log?.set('$el', $frame).end() | ||
const contentWindow = $frame.prop('contentWindow') | ||
return Cypress.$(contentWindow.document.body) | ||
}) | ||
}) | ||
|
||
// This can be used to execute a group of commands within an iframe | ||
// - This will verify that the iframe is loaded to any page other than 'about:blank' | ||
// cy.enter().then(getBody => { | ||
// getBody().find('.some-button').should('be.visible').click() | ||
// getBody().contains('Some hidden element').should('not.be.visible') | ||
// }) | ||
// - You can also give it a selector to find elements inside of a specific iframe | ||
// cy.enter('#my-iframe').then(getBody => { | ||
// getBody().find('.some-button').should('be.visible').click() | ||
// getBody().contains('Some hidden element').should('not.be.visible') | ||
// }) | ||
Cypress.Commands.add('enter', (selector, opts) => { | ||
if (selector === undefined) { | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} else if (typeof selector === 'object') { | ||
opts = selector | ||
selector = DEFAULT_IFRAME_SELECTOR | ||
} | ||
|
||
const fullOpts = { | ||
...DEFAULT_OPTS, | ||
...opts, | ||
} | ||
|
||
const log = fullOpts.log | ||
? Cypress.log({ | ||
name: 'enter', | ||
displayName: 'enter', | ||
message: [selector], | ||
}).snapshot() | ||
: null | ||
|
||
return cy.iframe(selector, { ...fullOpts, log: false }).then($body => { | ||
log?.set('$el', $body).end() | ||
return () => cy.wrap($body, { log: false }) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import '@testing-library/cypress/add-commands' | ||
import './iframe' |
Oops, something went wrong.