Skip to content

Commit

Permalink
Merge pull request #306 from tautastic/add-cypress
Browse files Browse the repository at this point in the history
Add cypress
  • Loading branch information
joneugster authored Mar 3, 2025
2 parents e14888b + 858348c commit e407bb2
Show file tree
Hide file tree
Showing 9 changed files with 1,588 additions and 36 deletions.
73 changes: 73 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Test
run-name: Test end-to-end
on:
workflow_dispatch:
push:
branches:
- "main"
pull_request:
branches:
- "main"
paths:
- ".github/workflows/test.yml"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

jobs:
test:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
browser:
- electron
- chrome
- firefox
include:
- os: ubuntu-latest
name: Linux
- os: macos-latest
name: macOS

name: ${{ matrix.name }} - ${{ matrix.browser }}
runs-on: ${{ matrix.os }}
continue-on-error: false
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
submodules: true

- name: Setup firefox (macOS)
if: matrix.os == 'macos-latest' && matrix.browser == 'firefox'
uses: browser-actions/setup-firefox@v1

- uses: leanprover/lean-action@v1
if: matrix.os != 'windows-latest'
with:
lake-package-directory: "server"
use-mathlib-cache: false
auto-config: false
build: true
test: false
lint: false

- uses: actions/setup-node@v4
- run: npm install

- name: Run tests
if: matrix.os != 'windows-latest'
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'npx wait-on tcp:8080'
browser: ${{ matrix.browser }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ server/.lake
logs/
relay/prev_cpu_metric
**/.i18n
.idea
13 changes: 13 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "cypress";

// default timeout was 4000.
// Infoview loading is slow on Windows…

export default defineConfig({
defaultCommandTimeout: 40000,
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
85 changes: 85 additions & 0 deletions cypress/e2e/spec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
describe('The world selection', () => {
it('displays the game title', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
cy.contains('span.nav-title', 'Test Game').should('exist')
})

it('displays the rules popup', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
cy.contains('a.nav-button', 'Rules').click()
cy.contains('div.modal', 'Game Rules').should('exist')
})

it('displays the world titles', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
cy.get('p.world-title')
.map('textContent')
.should('have.members', ['Demo World 1', 'Demo World 2']);
})

it('displays the first world and its first level as selectable', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
const world = cy.contains('svg.world-selection>a:not(.disabled)', 'Demo World 1')
const assertOn = [world, world.next('a.level')]

assertOn.forEach((value) => value
.should('have.attr', 'href', '#/g/test/Test/world/DemoWorld1/level/0')
.and('have.css', 'cursor', 'pointer')
)
})

it('displays the second world and its first level as disabled', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
const world = cy.contains('svg.world-selection>a.disabled', 'Demo World 2')
const assertOn = [world, world.next('a.level.disabled')]

assertOn.forEach((value) => value
.should('have.attr', 'href', '#/g/test/Test')
.and('have.css', 'cursor', 'default')
)
})
})

describe('The first level', () => {
it('can be navigated to from the world selection', () => {
cy.visit('http://localhost:3000/#/g/test/Test')
cy.contains('p.world-title', 'Demo World 1').click()
cy.location().its("hash").should('eq', '#/g/test/Test/world/DemoWorld1/level/0')
})

it('displays world and level title', () => {
cy.visit('http://localhost:3000/#/g/test/Test/world/DemoWorld1/level/0')
cy.contains('div.nav-content', 'Demo World 1')
.children()
.map((v) => v.textContent.trim())
.should('have.members', ['Demo World 1', 'Introduction', 'Start'])
})

it('displays the inventory tabs', () => {
cy.visit('http://localhost:3000/#/g/test/Test/world/DemoWorld1/level/0')
cy.get('div.tab')
.map((v) => v.textContent.trim())
.should('have.members', ['Theorems', 'Tactics', 'Definitions'])
})

it('displays the theorems inventory', () => {
cy.visit('http://localhost:3000/#/g/test/Test/world/DemoWorld1/level/0')
cy.contains('div.tab', 'Theorems').click()
cy.contains('div.inventory-list>div.item', 'demo_statement').click()
cy.contains('div.documentation>h1', 'demo_statement').should('exist')
cy.get('div.documentation>*>svg.fa-xmark').click()
})

it('displays the tactics inventory', () => {
cy.visit('http://localhost:3000/#/g/test/Test/world/DemoWorld1/level/0')
cy.contains('div.tab', 'Tactics').click()
cy.get('div.inventory-list>div.item')
.map((v) => v.textContent.trim())
.should('have.members', ['exact', 'rfl', 'rw'])
cy.contains('div.inventory-list>div.item', 'exact').click()
cy.contains('div.documentation>h1', 'exact').should('exist')
cy.get('div.documentation>div.markdown')
.contains('exact e closes the main goal if its target type matches that of e.')
.should('exist')
})
})
65 changes: 65 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/// <reference types="cypress" />

import 'cypress-iframe';

// ***********************************************
// 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>
// }
// }
// }

type Flatten<T> = T extends Iterable<infer Item> ? Item : never;
type ArrayIterator<T, TResult> = (value: T, index: number, collection: T[]) => TResult;

declare global {
namespace Cypress {
interface Chainable<Subject> {
map<Item extends Flatten<Subject>, K extends keyof Item>(iteratee: K): Chainable<Item[K][]>;
map<Item extends Flatten<Subject>, TResult>(iteratee: ArrayIterator<Item, TResult>): Chainable<TResult[]>;
}
}
}

Cypress.Commands.add('map', { prevSubject: true }, (subject: unknown[], iteratee) => {
return cy.wrap(
Cypress._.map(subject, iteratee),
{ log: false }
);
});

Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false;
});
20 changes: 20 additions & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// 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
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
8 changes: 8 additions & 0 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}
Loading

0 comments on commit e407bb2

Please sign in to comment.