Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] test: setup cypress for e2e testing #120

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
20 changes: 17 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
with:
version: 7
run_install: false

- name: Get pnpm store directory
id: pnpm-cache
run: |
Expand All @@ -43,16 +43,30 @@ jobs:
- name: Format with prettier
run: pnpm run -r fmt:check

- name: Test
- name: Unit Test (Backend)
run: pnpm --filter "{packages/backend}" test
env:
NODE_ENV: test

- name: Collect coverage
- name: Collect coverage (Backend)
run: pnpm --filter "{packages/backend}" test:coverage
env:
NODE_ENV: test

- name: Cypress pnpm patch
run: cp pnpm-lock.yaml package-lock.json

- name: E2E Test (Frontend)
uses: cypress-io/[email protected]
with:
browser: chrome
working-directory: src/packages/frontend/cypress
install-command: echo
build: pnpm --dir .. build
start: pnpm --dir .. start
wait-on: 'http://localhost:4173'
component: false

Comment on lines +57 to +69
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong yaml indentation

- name: Send coverage report to Codecov
uses: codecov/codecov-action@v2

Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,30 @@ jobs:
- name: Format with prettier
run: pnpm run -r fmt:check

- name: Test
- name: Unit Test (Backend)
run: pnpm --filter "{packages/backend}" test
env:
NODE_ENV: test

- name: Collect coverage
- name: Collect coverage (Backend)
run: pnpm --filter "{packages/backend}" test:coverage
env:
NODE_ENV: test

- name: Cypress pnpm patch
run: cp pnpm-lock.yaml package-lock.json

- name: E2E Test (Frontend)
uses: cypress-io/[email protected]
with:
browser: chrome
working-directory: src/packages/frontend/cypress
install-command: echo
build: pnpm --dir .. build
start: pnpm --dir .. start
wait-on: 'http://localhost:4173'
component: false

- name: Send coverage report to Codecov
uses: codecov/codecov-action@v2

Expand Down
43 changes: 22 additions & 21 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,42 +20,43 @@
},
"dependencies": {
"@logtail/node": "0.1.12",
"@sentry/node": "^7.7.0",
"@sentry/tracing": "^7.7.0",
"@tinyhttp/proxy-addr": "2.0.4",
"dotenv": "16.0.1",
"@sentry/node": "^7.15.0",
"@sentry/tracing": "^7.15.0",
"@tinyhttp/proxy-addr": "2.0.5",
"dotenv": "16.0.3",
"flourite": "1.2.2",
"gura": "1.4.4",
"helmet": "5.1.0",
"helmet": "6.0.0",
"polka": "1.0.0-next.22",
"rate-limiter-flexible": "2.3.7",
"rate-limiter-flexible": "2.3.12",
"shared": "link:../shared",
"sharp": "0.30.7",
"shiki": "0.10.1",
"sharp": "0.31.1",
"shiki": "0.11.1",
"sirv": "2.0.2",
"toml": "3.0.0",
"yaml": "2.1.1",
"zod": "^3.17.9"
"yaml": "2.1.3",
"zod": "^3.19.1"
},
"devDependencies": {
"@logtail/types": "^0.1.12",
"@types/node": "18.0.6",
"@types/sharp": "0.30.4",
"@types/node": "18.11.0",
"@types/sharp": "0.31.0",
"@types/supertest": "2.0.12",
"@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.6",
"c8": "^7.11.3",
"esbuild": "0.14.49",
"eslint": "8.20.0",
"@typescript-eslint/eslint-plugin": "5.40.0",
"@typescript-eslint/parser": "5.40.0",
"@vitest/coverage-c8": "^0.24.3",
"c8": "^7.12.0",
"esbuild": "0.15.11",
"eslint": "8.25.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1",
"esno": "^0.16.3",
"nodemon": "2.0.19",
"nodemon": "2.0.20",
"prettier": "2.7.1",
"supertest": "6.2.4",
"supertest": "6.3.0",
"tslib": "2.4.0",
"typescript": "4.7.4",
"vitest": "^0.18.1"
"typescript": "4.8.4",
"vitest": "^0.24.3"
},
"engineStrict": true,
"engines": {
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
dist
dist
cypress/videos
cypress/screenshots
10 changes: 10 additions & 0 deletions packages/frontend/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
config.baseUrl = 'http://localhost:5173';
return config;
},
},
});
15 changes: 15 additions & 0 deletions packages/frontend/cypress/e2e/documentation.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference types="cypress" />

beforeEach(() => {
cy.visit('/');
});

describe('Documentation', () => {
it('shows documentation title', () => {
cy.get('#documentation').contains('Documentation for Graphene API');
});

it('shows several API options documentation', () => {
cy.get('.OptionItem').should('have.length.gt', 0);
});
});
154 changes: 154 additions & 0 deletions packages/frontend/cypress/e2e/editor.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/// <reference types="cypress" />

beforeEach(() => {
cy.visit('/');
});

describe('Editor', () => {
it('shows the Editor component', () => {
cy.get('.Editor').should('be.visible');
});

it('shows the preview placeholder', () => {
cy.get('.preview-placeholder').should('be.visible');
});

it('shows setting popup toggle button', () => {
cy.get('.SettingsPopup').should('be.visible');
});

it('shows popup menu when toggle button is clicked', () => {
cy.get('.SettingsPopup > button').click();
cy.get('.popup').should('be.visible');
});

it('should be able to use the textarea to type the code', () => {
cy.get('.editor-input').type("console.log('foo');");
});
});

describe('Autocomplete', () => {
it('shows autocomplete menu when dropdown is clicked', () => {
cy.get('.Options .input').first().click();
cy.get('.candidates li').should('have.length.gt', 1);
});

it('should be able to select one of the option', () => {
cy.get('.Options .input').first().should('have.attr', 'placeholder', 'github dark dimmed').click();
cy.get('.candidates > li').first().click();
cy.get('.Options .input').first().should('have.attr', 'placeholder', 'css variables');
});

it('should be able to select one of the suggested option', () => {
cy.get('.Options .input').first().should('have.attr', 'placeholder', 'github dark dimmed').click().type('github');
cy.get('.candidates li').last().click();
cy.get('.Options .input').first().should('have.attr', 'placeholder', 'github light');
});

it('shows suggestions when some text is inserted', () => {
const incompleteTexts = [
// theme
'github',
// language
'script',
// font family
'mono',
// output format
'png',
];

// desktop
cy.viewport('macbook-13');
for (const [index, text] of incompleteTexts.entries()) {
const input = cy.get('.Options .input').eq(index);
input.focus().type(text);
cy.get('.candidates li')
.should('have.length.at.least', 1)
.each(($li) => cy.wrap($li).contains(text));
input.blur();
}

// mobile
cy.viewport('iphone-xr');
cy.get('.SettingsPopup > button').click();
for (const [index, text] of incompleteTexts.entries()) {
const input = cy.get('.Options .input').eq(index);
input.focus().type(text);
cy.get('.candidates li')
.should('have.length.at.least', 1)
.each(($li) => cy.wrap($li).contains(text));
input.blur();
}
});

it('should not show any suggestion on garbled input', () => {
cy.get('.Options .input').first().type('alksjdlaksjdlkajsdlkaj');
cy.get('.candidate li').should('have.length.lt', 1);
});
});

describe('Responsive Dropdown', () => {
type Screen = { width: number; height: number; optionAmount: number };

it('shows different amount of dropdown inputs based on screen size', () => {
const screens: Screen[] = [
// desktop 1080p / FHD screen
{ width: 1920, height: 1080, optionAmount: 4 },
// desktop 768p / HD screen
{ width: 1366, height: 768, optionAmount: 4 },
// large screen
{ width: 1100, height: 720, optionAmount: 3 },
// medium screen
{ width: 1000, height: 720, optionAmount: 2 },
// small screen
{ width: 800, height: 600, optionAmount: 1 },
// extra small screen
{ width: 600, height: 400, optionAmount: 0 },
];

for (const screen of screens) {
cy.viewport(screen.width, screen.height);
cy.get('.Options').should('have.length', screen.optionAmount);
}
});

it('shows different amount of dropdown inside the popup menu based on screen size', () => {
const screens: Screen[] = [
// desktop 1080p / FHD screen
{ width: 1920, height: 1080, optionAmount: 0 },
// desktop 768p / HD screen
{ width: 1366, height: 768, optionAmount: 0 },
// large screen
{ width: 1100, height: 720, optionAmount: 1 },
// medium screen
{ width: 1000, height: 720, optionAmount: 2 },
// small screen
{ width: 800, height: 600, optionAmount: 3 },
// extra small screen
{ width: 600, height: 400, optionAmount: 4 },
];

cy.get('.SettingsPopup > button').click();
for (const screen of screens) {
cy.viewport(screen.width, screen.height);
cy.get('.popup > .Options').should('have.length', screen.optionAmount);
}
});
});

describe('Popup menu', () => {
it('should be able to select upscale option', () => {
cy.get('.SettingsPopup > button').click();
const upscaleItem = cy.get('.upscale > .upscale-item').last();
upscaleItem.last().should('have.css', 'opacity', '0.5');
upscaleItem.last().click();
upscaleItem.last().should('have.css', 'opacity', '1');
});

it('should be able to input border thickness and radius', () => {
cy.get('.SettingsPopup > button').click();
cy.get('.border-input').each(($input) => {
cy.wrap($input).type('12');
});
});
});
18 changes: 18 additions & 0 deletions packages/frontend/cypress/e2e/header.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference types="cypress" />

beforeEach(() => {
cy.visit('/');
});

describe('Header', () => {
it('shows the app title and subtitle', () => {
cy.get('.title').contains('GRAPHENE');
cy.get('.subtitle').contains('Create and share beautiful code snippets!');
});

it('shows a github button to graphene repository', () => {
cy.get('.button')
.contains('See on Github')
.should('have.attr', 'href', 'https://github.com/teknologi-umum/graphene');
});
});
5 changes: 5 additions & 0 deletions packages/frontend/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
37 changes: 37 additions & 0 deletions packages/frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
Loading