Skip to content

Commit

Permalink
[explorer] Migrate from Puppeteer to Cypress (MystenLabs#3837)
Browse files Browse the repository at this point in the history
* Rework puppeer tests to cypress

* Fix commands in package.json

* Update github workflow

* Fix missing build dependencies

* Run correct server and tests

* Stop running build before e2e

* Revert change

* Update cypress tests based on feedback

* Prettier

* Lock pagination tests to NFTs

* Remove unused dependency

* Update PR based on recent changes

* Resolve rebase

* Format
  • Loading branch information
Jordan-Mysten authored Aug 18, 2022
1 parent 53af4ae commit 24b92aa
Show file tree
Hide file tree
Showing 14 changed files with 1,000 additions and 857 deletions.
27 changes: 25 additions & 2 deletions .github/workflows/explorer-client-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,32 @@ jobs:
- name: Lint
working-directory: ./explorer/client
run: yarn lint
- name: Test
- name: Unit Tests
working-directory: ./explorer/client
run: yarn test
run: yarn test:unit
- name: Build
working-directory: ./explorer/client
run: yarn build
end_to_end:
name: End-to-end tests
needs: diff
if: needs.diff.outputs.isClient == 'true'
runs-on: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Nodejs
uses: actions/setup-node@v3
with:
node-version: "16"
cache: "yarn"
cache-dependency-path: ./explorer/client/yarn.lock
- name: Build TS sdk
working-directory: ./sdk/typescript
run: yarn install; yarn build
- name: Run e2e tests
uses: cypress-io/github-action@v4
with:
install-command: yarn --frozen-lockfile
start: yarn start:static
working-directory: ./explorer/client
2 changes: 2 additions & 0 deletions explorer/client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
cypress/screenshots
cypress/videos
16 changes: 16 additions & 0 deletions explorer/client/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:8080',
},
component: {
devServer: {
framework: 'create-react-app',
bundler: 'webpack',
},
},
});
264 changes: 264 additions & 0 deletions explorer/client/cypress/e2e/e2e.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// Standardized CSS Selectors
const mainBodyCSS = 'main > section > div';
const nftObject = (num: number) => `div#ownedObjects > div:nth-child(${num}) a`;
const ownerButton = 'td#owner span:first-child';

// Standardized expectations:
const expectHome = () => {
cy.get(mainBodyCSS).invoke('attr', 'data-testid').should('eq', 'home-page');
};

const expectErrorResult = () => {
cy.get(mainBodyCSS).invoke('attr', 'id').should('eq', 'errorResult');
};

const searchText = (text: string) => {
// TODO: Ideally this should just call `submit` but the search isn't a form yet:
cy.get('#searchText').type(text).get('#searchBtn').click();
};

describe('End-to-end Tests', () => {
describe('The Home Page', () => {
it('is the landing page', () => {
cy.visit('/');
expectHome();
});

it('is the redirect page', () => {
cy.visit('/apples');
expectHome();
});

it('has a go home button', () => {
cy.visit('/objects/CollectionObject');
cy.get('#homeBtn').click();
expectHome();
});
});

describe('Wrong Search', () => {
it('leads to error page', () => {
cy.visit('/');
searchText('apples');
expectErrorResult();
});
});

describe('Object Results', () => {
const successObjectID = 'CollectionObject';
const problemObjectID = 'ProblemObject';

it('can be searched', () => {
cy.visit('/');
searchText(successObjectID);
cy.get('#objectID').contains(successObjectID);
});

it('can be reached through URL', () => {
cy.visit(`/objects/${successObjectID}`);
cy.get('#objectID').contains(successObjectID);
});

it('can have missing data', () => {
cy.visit(`/objects/${problemObjectID}`);
expectErrorResult();
});
});

describe('Address Results', () => {
const successAddressID = 'receiverAddress';
const noObjectsAddressID = 'senderAddress';

it('can be searched', () => {
cy.visit('/');
searchText(successAddressID);
cy.get('#addressID').contains(successAddressID);
});

it('can be reached through URL', () => {
cy.visit(`/addresses/${successAddressID}`);
cy.get('#addressID').contains(successAddressID);
});

it('displays error when no objects', () => {
cy.visit(`/objects/${noObjectsAddressID}`);
expectErrorResult();
});
});

describe('Transaction Results', () => {
const successID = 'Da4vHc9IwbvOYblE8LnrVsqXwryt2Kmms+xnJ7Zx5E4=';
it('can be searched', () => {
cy.visit('/');
searchText(successID);
cy.get('[data-testid=transaction-id]').contains(successID);
});

it('can be reached through URL', () => {
cy.visit(`/transactions/${successID}`);
cy.get('[data-testid=transaction-id]').contains(successID);
});

it('includes the sender time information', () => {
cy.visit(`/transactions/${successID}`);
cy.get('[data-testid=transaction-sender]').contains(
'Sun, 15 Dec 2024 00:00:00 GMT'
);
});
});

describe('Owned Objects have links that enable', () => {
it('going from address to object and back', () => {
cy.visit('/addresses/receiverAddress');
cy.get(nftObject(1)).click();
cy.get('#objectID').contains('player1');
cy.get(ownerButton).click();
cy.get('#addressID').contains('receiverAddress');
});

it('going from object to child object and back', () => {
cy.visit('/objects/player2');
cy.get(nftObject(1)).click();
cy.get('#objectID').contains('Image1');
cy.get(ownerButton).click();
cy.get('#objectID').contains('player2');
});

it('going from parent to broken image object and back', () => {
const parentValue = 'ObjectWBrokenChild';
cy.visit(`/objects/${parentValue}`);
cy.get(nftObject(1)).click();
cy.get('#noImage');
cy.get(ownerButton).click();
cy.get('#loadedImage');
});
});

describe('PaginationWrapper has buttons', () => {
const paginationContext = '#NFTSection';

it('to go to the next page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
cy.get('[data-testid=nextBtn]:visible').click();
cy.get(nftObject(1)).click();
});
cy.get('#objectID').contains('Image2');
});

it('to go to the last page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
cy.get('[data-testid=lastBtn]:visible').click();
cy.get(nftObject(1)).click();
});
cy.get('#objectID').contains('CollectionObject');
});

it('where last and next disappear in final page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
cy.get('[data-testid=lastBtn]:visible').click();

//Back and First buttons are not disabled:
cy.get('[data-testid=backBtn]:visible').should('be.enabled');
cy.get('[data-testid=firstBtn]:visible').should('be.enabled');

//Next and Last buttons are disabled:
cy.get('[data-testid=nextBtn]:visible').should('be.disabled');
cy.get('[data-testid=lastBtn]:visible').should('be.disabled');
});
});

it('to go back a page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
cy.get('[data-testid=lastBtn]:visible').click();
cy.get('[data-testid=backBtn]:visible').click();
cy.get(nftObject(1)).click();
});
cy.get('#objectID').contains('player5');
});

it('to go to first page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
cy.get('[data-testid=lastBtn]:visible').click();
cy.get('[data-testid=backBtn]:visible').click();
cy.get('[data-testid=firstBtn]:visible').click();
});
cy.get(nftObject(1)).click();
cy.get('#objectID').contains('ChildObjectWBrokenImage');
});

it('where first and back disappear in first page', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);
cy.get(paginationContext).within(() => {
//Back and First buttons are disabled:
cy.get('[data-testid=backBtn]:visible').should('be.disabled');
cy.get('[data-testid=firstBtn]:visible').should('be.disabled');

//Next and Last buttons are not disabled:
cy.get('[data-testid=nextBtn]:visible').should('be.enabled');
cy.get('[data-testid=lastBtn]:visible').should('be.enabled');
});
});
});

describe('Group View', () => {
it('evaluates balance', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);

// TODO: Add test IDs to make this selection less structural
cy.get(
'#groupCollection > div:nth-child(2) > div:nth-child(1) > div'
)
.children()
.eq(1)
.contains('0x2::USD::USD')
.next()
.contains('2')
.next()
.contains('9007199254740993');

cy.get(
'#groupCollection > div:nth-child(2) > div:nth-child(2) > div'
)
.children()
.eq(1)
.contains('SUI')
.next()
.contains('2')
.next()
.contains('200');
});
});

// TODO: This test isn't great, ideally we'd either do some more manual assertions, validate linking,
// or use visual regression testing.
describe('Transactions for ID', () => {
it('are displayed from and to address', () => {
const address = 'ownsAllAddress';
cy.visit(`/addresses/${address}`);

cy.get('[data-testid=tx] td').should('have.length.greaterThan', 0);
});

it('are displayed for input and mutated object', () => {
const address = 'CollectionObject';
cy.visit(`/addresses/${address}`);

cy.get('[data-testid=tx] td').should('have.length.greaterThan', 0);
});
});
});
17 changes: 17 additions & 0 deletions explorer/client/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
8 changes: 8 additions & 0 deletions explorer/client/cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress", "node"],
"isolatedModules": false
},
"include": ["./**/*"]
}
6 changes: 0 additions & 6 deletions explorer/client/jest.config.js

This file was deleted.

12 changes: 6 additions & 6 deletions explorer/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@
"@testing-library/user-event": "^13.5.0",
"@types/bn.js": "^5.1.0",
"@types/jest": "^27.4.0",
"@types/jest-environment-puppeteer": "^5.0.0",
"@types/node": "^16.11.24",
"@types/puppeteer": "^5.4.5",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"@types/topojson-client": "^3.1.1",
"autoprefixer": "^10.4.2",
"cypress": "^10.4.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-header": "^3.1.1",
"jest-puppeteer": "^6.1.0",
"onchange": "^7.1.0",
"postcss": "^8.4.6",
"prettier": "2.5.1",
"puppeteer": "^13.5.1",
"react-scripts": "5.0.1",
"start-server-and-test": "^1.14.0",
"stylelint": "^14.5.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^25.0.0",
Expand Down Expand Up @@ -55,8 +53,10 @@
"scripts": {
"start": "react-scripts start",
"start:static": "REACT_APP_DATA=static PORT=8080 react-scripts start",
"start:local": "REACT_APP_DATA=local PORT=8080 react-scripts start",
"test": "npx start-server-and-test 'yarn start:static' 8080 'react-scripts test --detectOpenHandles --watchAll=false'",
"start:local": "REACT_APP_DATA=local react-scripts start",
"test": "yarn test:unit && yarn test:cypress",
"test:unit": "react-scripts test --detectOpenHandles --watchAll=false",
"test:cypress": "start-server-and-test start:static 8080 'cypress run'",
"build": "react-scripts build",
"build:staging": "REACT_APP_DATA=staging react-scripts build",
"build:prod": "REACT_APP_DATA=prod react-scripts build",
Expand Down
Loading

0 comments on commit 24b92aa

Please sign in to comment.