Skip to content

Commit

Permalink
test: Add CCTP E2E tests to CI (#1843)
Browse files Browse the repository at this point in the history
Co-authored-by: Dewansh <[email protected]>
Co-authored-by: Fionna Chan <[email protected]>
  • Loading branch information
3 people authored Sep 9, 2024
1 parent ca78d06 commit f892b6a
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 188 deletions.
29 changes: 12 additions & 17 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,17 @@ jobs:
uses: actions/checkout@v4

- id: set-matrix
run: |
content=`cat packages/arb-token-bridge-ui/tests/e2e/specfiles.json | jq --compact-output .`
echo "e2eFiles=$content" >> $GITHUB_OUTPUT
run: echo "e2eFiles=$(node .github/workflows/formatSpecfiles.js | jq . --compact-output)" >> $GITHUB_OUTPUT

# based on https://github.com/Synthetixio/synpress/blob/dev/.github/workflows/e2e_cypress-action.yml
test-e2e:
name: "Test E2E${{ matrix.orbit-test == '1' && ' with L3' || '' }} - ${{ matrix.tests.name }}"
name: "Test E2E ${{ (matrix.test.type == 'cctp' && 'CCTP') || (matrix.test.orbitTest == '1' && 'with L3') || '' }} - ${{ matrix.test.name }}"
runs-on: ubuntu-latest
needs: [build, check-files, check-is-hotfix, load-e2e-files]
if: needs.check-files.outputs.run_tests == 'true' && needs.check-is-hotfix.outputs.is_hotfix == 'false'
strategy:
fail-fast: false # If one test fails, let the other tests run
matrix:
tests:
${{ fromJson(needs.load-e2e-files.outputs.matrix) }}
orbit-test: ['0', '1']
test: ${{ fromJson(needs.load-e2e-files.outputs.matrix) }}

steps:
- name: Free Disk Space (Ubuntu)
Expand Down Expand Up @@ -214,27 +209,29 @@ jobs:
DISPLAY: :0.0

- name: Run nitro testnode
if: matrix.test.type == 'regular'
uses: OffchainLabs/actions/run-nitro-test-node@a20a76172ce524832ac897bef2fa10a62ed81c29
with:
nitro-testnode-ref: aab133aceadec2e622f15fa438f6327e3165392d
l3-node: ${{ matrix.orbit-test == '1' }}
no-l3-token-bridge: ${{ matrix.orbit-test == '0' }}
l3-node: ${{ matrix.test.orbitTest == '1' }}
no-l3-token-bridge: ${{ matrix.test.orbitTest == '0' }}

- name: Run e2e tests via cypress-io/github-action
uses: cypress-io/github-action@8d3918616d8ac34caa2b49afc8b408b6a872a6f5 # [email protected]
with:
start: yarn start
command: ${{ matrix.orbit-test == '1' && 'yarn test:e2e:orbit --browser chromium' || 'yarn test:e2e --browser chromium' }}
command: "yarn test:e2e${{ (matrix.test.type == 'cctp' && ':cctp') || (matrix.test.orbitTest == '1' && ':orbit') || '' }} --browser chromium"
wait-on: http://127.0.0.1:3000
wait-on-timeout: 120
spec: ./packages/arb-token-bridge-ui/tests/e2e/specs/*
env:
DISPLAY: :0.0
TEST_FILE: ${{ matrix.tests.file }}
TEST_FILE: ${{ matrix.test.file }}
SKIP_METAMASK_SETUP: true
CYPRESS_RECORD_VIDEO: ${{ matrix.tests.recordVideo }}
CYPRESS_RECORD_VIDEO: ${{ matrix.test.recordVideo }}
PRIVATE_KEY_CUSTOM: ${{ secrets.E2E_PRIVATE_KEY }}
PRIVATE_KEY_USER: ${{ secrets.E2E_PRIVATE_KEY_USER }}
PRIVATE_KEY_CCTP: ${{ secrets.E2E_PRIVATE_KEY_CCTP }}
NEXT_PUBLIC_IS_E2E_TEST: true
NEXT_PUBLIC_INFURA_KEY: ${{ secrets.NEXT_PUBLIC_INFURA_KEY }}
NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL: http://127.0.0.1:8545
Expand All @@ -245,17 +242,16 @@ jobs:
uses: actions/upload-artifact@v4
if: always()
with:
name: e2e-artifacts-pull-request-${{ github.event.pull_request.number }}-commit-${{ github.sha }}-${{ matrix.tests.name }}
name: e2e-artifacts-pull-request-${{ github.event.pull_request.number }}-commit-${{ github.sha }}-${{ matrix.test.name }}-${{ matrix.test.type }}-${{ matrix.test.orbitTest }}
path: |
./packages/arb-token-bridge-ui/cypress/videos
./packages/arb-token-bridge-ui/cypress/screenshots
if-no-files-found: 'ignore'
continue-on-error: true

- name: Throw error if tests failed
if: steps.e2e-run.outputs.status == 'failed'
run: exit 1

test-e2e-success:
name: "Test E2E Success"
runs-on: ubuntu-latest
Expand All @@ -269,7 +265,6 @@ jobs:
- name: E2E Failed
if: needs.test-e2e.result != 'success' && needs.test-e2e.result != 'skipped'
run: exit 1

clean-up:
name: "Clean Up"
runs-on: ubuntu-latest
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/formatSpecfiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env node
const specFiles = require('../../packages/arb-token-bridge-ui/tests/e2e/specfiles.json')
const cctpFiles = require('../../packages/arb-token-bridge-ui/tests/e2e/cctp.json')

// For each test in specFiles, add an orbit test
const tests = []
specFiles.forEach(spec => {
tests.push({
...spec,
type: 'regular',
orbitTest: '0',
})
tests.push({
...spec,
type: 'regular',
orbitTest: '1',
})
})

cctpFiles.forEach(spec => {
tests.push({
...spec,
type: 'cctp',
orbitTest: null,
})
})

console.log(JSON.stringify(tests))
16 changes: 8 additions & 8 deletions packages/arb-token-bridge-ui/synpress.cctp.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,6 @@ if (typeof INFURA_KEY === 'undefined') {
throw new Error('Infura API key not provided')
}

const SEPOLIA_INFURA_RPC_URL = `https://sepolia.infura.io/v3/${INFURA_KEY}`
const sepoliaRpcUrl =
process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL ?? SEPOLIA_INFURA_RPC_URL
const arbSepoliaRpcUrl = 'https://sepolia-rollup.arbitrum.io/rpc'

const sepoliaProvider = new StaticJsonRpcProvider(sepoliaRpcUrl)
const arbSepoliaProvider = new StaticJsonRpcProvider(arbSepoliaRpcUrl)

if (!process.env.PRIVATE_KEY_CCTP) {
throw new Error('PRIVATE_KEY_CCTP variable missing.')
}
Expand All @@ -67,6 +59,14 @@ if (!process.env.PRIVATE_KEY_USER) {
throw new Error('PRIVATE_KEY_USER variable missing.')
}

const SEPOLIA_INFURA_RPC_URL = `https://sepolia.infura.io/v3/${INFURA_KEY}`
const sepoliaRpcUrl =
process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL ?? SEPOLIA_INFURA_RPC_URL
const arbSepoliaRpcUrl = 'https://sepolia-rollup.arbitrum.io/rpc'

const sepoliaProvider = new StaticJsonRpcProvider(sepoliaRpcUrl)
const arbSepoliaProvider = new StaticJsonRpcProvider(arbSepoliaRpcUrl)

// Wallet funded on Sepolia and ArbSepolia with ETH and USDC
const localWallet = new Wallet(process.env.PRIVATE_KEY_CCTP)
// Generate a new wallet every time
Expand Down
6 changes: 4 additions & 2 deletions packages/arb-token-bridge-ui/tests/e2e/cctp.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[
{
"name": "Deposit Cctp",
"file": "tests/e2e/specs/**/depositCctp.cy.{js,jsx,ts,tsx}"
"file": "tests/e2e/specs/**/depositCctp.cy.{js,jsx,ts,tsx}",
"recordVideo": false
},
{
"name": "Withdraw Cctp",
"file": "tests/e2e/specs/**/withdrawCctp.cy.{js,jsx,ts,tsx}"
"file": "tests/e2e/specs/**/withdrawCctp.cy.{js,jsx,ts,tsx}",
"recordVideo": false
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ describe('Approve token for deposit', () => {
name: /Pay approval fee of/
}).click()
cy.confirmSpending('5')

/**
* If confirm spending fails, test is still considered to be passing by Cypress
* We add another check to make sure the test fails if needed
*/
cy.wait(10_000)
cy.rejectMetamaskTransaction()
})
Expand Down
143 changes: 65 additions & 78 deletions packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const confirmAndApproveCctpDeposit = () => {
.should('be.enabled')
.click()

cy.findByText(/I understand that I have to/).click()
cy.findByText(/I understand that I have to/)
.should('be.visible')
.click()
cy.findByRole('button', {
name: /Pay approval fee of/
}).click()
Expand All @@ -61,86 +63,71 @@ const confirmAndApproveCctpDeposit = () => {

describe('Deposit USDC through CCTP', () => {
// Happy Path
context('User has some USDC and is on L1', () => {
let USDCAmountToSend: number = 0

// log in to metamask before deposit
beforeEach(() => {
USDCAmountToSend = Number((Math.random() * 0.001).toFixed(6)) // randomize the amount to be sure that previous transactions are not checked in e2e

cy.fundUserWalletEth('parentChain')
cy.fundUserUsdcTestnet('parentChain')
cy.resetCctpAllowance('parentChain')

/// common code before all tests
cy.login({ networkType: 'parentChain', networkName: 'sepolia' })
context('should show L1 and L2 chains, and USD correctly', () => {
cy.findSourceChainButton('Sepolia')
cy.findDestinationChainButton('Arbitrum Sepolia')
cy.findSelectTokenButton('ETH')
})

cy.searchAndSelectToken({
tokenName: 'USDC',
tokenAddress: CommonAddress.Sepolia.USDC
})

context('should show summary', () => {
cy.typeAmount(USDCAmountToSend)
cy.findGasFeeSummary(zeroToLessThanOneETH)
cy.findGasFeeForChain('Sepolia', zeroToLessThanOneETH)
cy.findGasFeeForChain(
/You'll have to pay Arbitrum Sepolia gas fee upon claiming./i
)
})
})
const USDCAmountToSend = 0.0001

beforeEach(() => {
cy.login({ networkType: 'parentChain', networkName: 'sepolia' })
cy.findSourceChainButton('Sepolia')
cy.findDestinationChainButton('Arbitrum Sepolia')
cy.findSelectTokenButton('ETH')

it('should initiate depositing USDC to the same address through CCTP successfully', () => {
context('should show clickable deposit button', () => {
cy.findMoveFundsButton().click()
})

context('Should display CCTP modal', () => {
confirmAndApproveCctpDeposit()
cy.confirmMetamaskPermissionToSpend(USDCAmountToSend.toString())

// eslint-disable-next-line
cy.wait(40_000)
cy.confirmMetamaskTransaction()
cy.findTransactionInTransactionHistory({
duration: 'a minute',
amount: USDCAmountToSend,
symbol: 'USDC'
})
})
cy.searchAndSelectToken({
tokenName: 'USDC',
tokenAddress: CommonAddress.Sepolia.USDC
})

it('should initiate depositing USDC to custom destination address through CCTP successfully', () => {
context('should fill custom destination address successfully', () => {
cy.fillCustomDestinationAddress()
})

context('should click deposit successfully', () => {
cy.findMoveFundsButton().click()
})

context('Should display CCTP modal', () => {
confirmAndApproveCctpDeposit()
cy.confirmMetamaskPermissionToSpend(USDCAmountToSend.toString())

// eslint-disable-next-line
cy.wait(40_000)
cy.confirmMetamaskTransaction()
const txData = { amount: USDCAmountToSend, symbol: 'USDC' }
cy.findTransactionInTransactionHistory({
duration: 'a minute',
...txData
})
cy.openTransactionDetails(txData)
cy.findTransactionDetailsCustomDestinationAddress(
Cypress.env('CUSTOM_DESTINATION_ADDRESS')
)
})
cy.typeAmount(USDCAmountToSend)
cy.findGasFeeSummary(zeroToLessThanOneETH)
cy.findGasFeeForChain('Sepolia', zeroToLessThanOneETH)
cy.findGasFeeForChain(
/You'll have to pay Arbitrum Sepolia gas fee upon claiming./i
)
})

it('should initiate depositing USDC to the same address through CCTP successfully', () => {
cy.findMoveFundsButton().click()

confirmAndApproveCctpDeposit()
cy.confirmSpending(USDCAmountToSend.toString())

/**
* Currently synpress cy.confirmMetamaskTransaction doesn't work on Sepolia
* CCTP flow is tested in withdrawCctp.cy.ts
*/
// cy.wait(40_000)
// cy.confirmMetamaskTransaction(undefined)
// cy.findTransactionInTransactionHistory({
// duration: 'a minute',
// amount: USDCAmountToSend,
// symbol: 'USDC',
// options: {
// timeout: 60_000
// }
// })
})

/**
* Because the previous test doesn't send any transaction, allowance is still valid here.
* Skipping the test for now
*/
it.skip('should initiate depositing USDC to custom destination address through CCTP successfully', () => {
cy.fillCustomDestinationAddress()
cy.findMoveFundsButton().click()
confirmAndApproveCctpDeposit()

cy.confirmSpending(USDCAmountToSend.toString())

cy.wait(40_000)
cy.confirmMetamaskTransaction(undefined)
const txData = { amount: USDCAmountToSend, symbol: 'USDC' }
cy.wait(15_000)
cy.findTransactionInTransactionHistory({
duration: 'a minute',
...txData
})
cy.openTransactionDetails(txData)
cy.findTransactionDetailsCustomDestinationAddress(
Cypress.env('CUSTOM_DESTINATION_ADDRESS')
)
})
})
Loading

0 comments on commit f892b6a

Please sign in to comment.