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

Playing around with end-to-end testing #503

Merged
merged 25 commits into from
Dec 9, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3402021
:sparkles: Initial implementation of a test mode
dtinth Oct 14, 2018
aaf9dd8
:green_heart: An E2E test that plays through the game!
dtinth Oct 14, 2018
6381e1a
Merge branch 'master' of github.com:bemusic/bemuse into e2e-testing
dtinth Dec 8, 2018
ccef366
:art: yarn style:fix
dtinth Dec 8, 2018
78ff50a
:fire: bin/setup
dtinth Dec 8, 2018
a4abd5d
:fire: Remove pre-commit npm script
dtinth Dec 8, 2018
fb6972f
:heavy_plus_sign: lint-staged and husky
dtinth Dec 8, 2018
1c753e7
:wrench: Turn off some lint rules not compatible with typescript parser
dtinth Dec 8, 2018
9e5f3c6
:art: .eslintrc{=>.yml}
dtinth Dec 8, 2018
f6b4187
:wrench: Turn on strict mode for TypeScript
dtinth Dec 8, 2018
7494f51
:construction_worker: Create E2E job for CircleCI
dtinth Dec 9, 2018
72ffb09
:ok_hand::rotating_light: Switch to use expect instead of built-in as…
dtinth Dec 9, 2018
c6f3eca
:construction_worker::zap: Skip installing Lerna for E2E
dtinth Dec 9, 2018
c1051bc
:wrench: Run Chrome in headless mode
dtinth Dec 9, 2018
df92b3f
:arrow_up: prescript@0.5555.55
dtinth Dec 9, 2018
9ea3edf
:arrow_up: puppeteer@1.11.0
dtinth Dec 9, 2018
ad90e39
:ok_hand: Reduce cognitive complexity in game-controller
dtinth Dec 9, 2018
b87be93
:hammer::art: Move conditionals into individual methods
dtinth Dec 9, 2018
913a027
:green_heart: Disable Chrome sandbox when running in CircleCI
dtinth Dec 9, 2018
362496c
:ok_hand: Reduce complexity in MusicChartSelectorItem
dtinth Dec 9, 2018
7a6b8fa
:bug::green_heart: Fix port when running E2E test
dtinth Dec 9, 2018
504df41
:sparkles: Allow specifying port using BEMUSE_PORT
dtinth Dec 9, 2018
00fac29
:wrench::zap::construction_worker: Reduce no_output_timeout to 1m
dtinth Dec 9, 2018
4b3d9e0
:wrench::construction_worker::green_heart: Set port for Bemuse when r…
dtinth Dec 9, 2018
5c6241e
:sparkles::construction_worker: Capture screenshot in E2E
dtinth Dec 9, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ rules:
node/no-deprecated-api: warn
no-sequences: off
import/named: warn
overrides:
- files:
- '**/*.ts'
- '**/*.tsx'
rules:
no-undef: off
no-unused-vars: off
no-use-before-define: off
9 changes: 0 additions & 9 deletions bin/setup

This file was deleted.

3 changes: 3 additions & 0 deletions e2e/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"include": ["tests"]
}
8 changes: 8 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "bemuse-e2e",
"version": "0.0.1",
"dependencies": {
"prescript": "0.5040.1-beta.10",
"puppeteer": "^1.9.0"
}
}
94 changes: 94 additions & 0 deletions e2e/tests/Gameplay-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const { action, defer, to } = require('prescript')
const puppeteer = require('puppeteer')
const assert = require('assert')

action('Open browser', async state => {
state.browser = await puppeteer.launch({ headless: false })
state.page = await state.browser.newPage()
await state.page.setViewport({ width: 1200, height: 480 })
await state.page.goto(
'http://localhost:8080/?server=https://raw.githubusercontent.com/bemusic/bemuse-test-server/master'
)
})

defer('Close browser', async state => {
await state.browser.close()
})

action('Turn on test mode', async state => {
await state.page.waitForFunction(
() => {
if (window.BemuseTestMode) {
window.BemuseTestMode.enableTestMode()
return true
} else {
return false
}
},
{ timeout: 10000 }
)
})

action('Enter game', async state => {
await state.page.waitForSelector('[data-testid="enter-game"]')
await state.page.click('[data-testid="enter-game"]')
await state.page.waitForSelector('[data-testid="keyboard-mode"]')
})

action('Select keyboard mode', async state => {
await state.page.waitForSelector('[data-testid="keyboard-mode"]')
await state.page.click('[data-testid="keyboard-mode"]')
await state.page.waitForSelector('[data-testid="play-selected-chart"]')
})

action('Select the song and start the game', async state => {
await state.page.waitForSelector('[data-testid="play-selected-chart"]')
await state.page.click('[data-testid="play-selected-chart"]')
await state.page.waitForSelector('.game-display canvas', { visible: true })
})

to('Play through the game', () => {
action('Press Enter to start the song', async state => {
await state.page.waitFor(100)
await state.page.keyboard.down('Enter')
await state.page.waitFor(100)
await state.page.keyboard.up('Enter')
})
const keys = [
{ key: 'j', beat: 4, expectedScore: 69444 },
{ key: 'f', beat: 5, expectedScore: 138888 },
{ key: 'f', beat: 6, expectedScore: 208333 },
{ key: 'k', beat: 7, expectedScore: 277777 },
{ key: 'j', beat: 8, expectedScore: 347221 },
{ key: 'f', beat: 8.5, expectedScore: 416666 },
{ key: 'f', beat: 9, expectedScore: 486110 },
{ key: 'k', beat: 9.75, expectedScore: 524305 },
]
for (const [i, event] of keys.entries()) {
const t = (event.beat * 60) / 140
action`Hit key ${event.key} at t=${t.toFixed(2)} (note #${i +
1}) (score should be ${event.expectedScore})`(async (state, context) => {
await state.page.evaluate(t => {
window.TEST_pausePromise = window.BemuseTestMode.pauseAt(t)
}, t)
await state.page.evaluate(() => window.TEST_pausePromise)
await state.page.keyboard.down(event.key)
await state.page.waitFor(100)
await state.page.keyboard.up(event.key)
const score = await state.page.evaluate(() =>
window.BemuseTestMode.getScore()
)
assert.equal(score, event.expectedScore)
dtinth marked this conversation as resolved.
Show resolved Hide resolved
})
}

action(
'Let the game finish and wait for result screen to show',
async state => {
await state.page.evaluate(() => {
window.BemuseTestMode.unpause()
})
await state.page.waitForSelector('.ResultScene')
}
)
})
12 changes: 12 additions & 0 deletions e2e/tests/state.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Browser, Page } from 'puppeteer'

declare global {
namespace Prescript {
interface GlobalState {
browser: Browser
page: Page
}
}
}

export {}
15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"private": true,
"workspaces": [
"website",
"e2e",
"packages/*"
],
"browserslist": [
Expand All @@ -22,7 +23,6 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx .",
"postbootstrap": "lerna run prepare",
"pre-commit": "gulp pre-commit",
"prepare": "lerna run prepare",
"prod-build": "cross-env NODE_ENV=production gulp build",
"prod-start": "cross-env NODE_ENV=production HOT=true gulp server",
Expand Down Expand Up @@ -89,6 +89,7 @@
"gulp-gh-pages": "git://github.com/dtinth/gulp-gh-pages",
"gulp-mocha": "^6.0.0",
"gulp-util": "^3.0.8",
"husky": "^1.2.0",
"import-sort-style-renke": "^2.4.0",
"istanbul-instrumenter-loader": "^3.0.1",
"jade": "^1.9.2",
Expand All @@ -103,6 +104,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^3.0.5",
"lerna": "^2.11.0",
"lint-staged": "^8.1.0",
"markdown-toc": "^1.2.0",
"merge-stream": "^1.0.1",
"minimatch": "^3.0.4",
Expand Down Expand Up @@ -192,5 +194,16 @@
"updeep": "^0.16.0",
"variance": "0.0.1",
"whatwg-fetch": "^1.1.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,json,css,md}": [
"prettier --write",
"git add"
]
}
}
5 changes: 0 additions & 5 deletions packages/bemuse-notechart/test/.eslintrc

This file was deleted.

2 changes: 2 additions & 0 deletions packages/bemuse-notechart/test/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
env:
mocha: true
5 changes: 5 additions & 0 deletions src/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { withContext } from 'recompose'
import * as Analytics from './analytics'
import * as OptionsIO from './io/OptionsIO'
import * as ReduxState from './redux/ReduxState'
import * as BemuseTestMode from '../devtools/BemuseTestMode'
import AboutScene from './ui/AboutScene'
import BrowserSupportWarningScene from './ui/BrowserSupportWarningScene'
import ModeSelectScene from './ui/ModeSelectScene'
Expand Down Expand Up @@ -132,3 +133,7 @@ function trackFullscreenEvents() {
window.addEventListener('beforeunload', () => {
Analytics.send('app', 'quit')
})

Object.assign(window, {
BemuseTestMode,
})
12 changes: 10 additions & 2 deletions src/app/ui/ModeSelectScene.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ class ModeSelectScene extends React.Component {
onTouchStart={() => this.setPlayDevice('touch')}
onMouseDown={() => this.setPlayDevice('keyboard')}
>
<div className='ModeSelectSceneのitem' onClick={this.handleKB}>
<div
className='ModeSelectSceneのitem'
onClick={this.handleKB}
data-testid='keyboard-mode'
>
{this.renderKBGraphics()}
<h2>Keyboard Mode</h2>
<p>
Expand All @@ -48,7 +52,11 @@ class ModeSelectScene extends React.Component {
</p>
<p>This mode is similar to O2Jam.</p>
</div>
<div className='ModeSelectSceneのitem' onClick={this.handleBM}>
<div
className='ModeSelectSceneのitem'
onClick={this.handleBM}
data-testid='bms-mode'
>
{this.renderBMGraphics()}
<h2>BMS Mode</h2>
<p>
Expand Down
6 changes: 5 additions & 1 deletion src/app/ui/MusicChartSelectorItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ class MusicChartSelectorItem extends React.Component {
'is-5keys': this.props.chart.keys === '5K',
})
return (
<li className={classes} onClick={this.handleClick}>
<li
className={classes}
onClick={this.handleClick}
data-testid={this.props.isSelected ? 'play-selected-chart' : undefined}
>
{this.props.isTutorial ? (
this.props.chart.keys === '5K' ? (
'Start Tutorial (5 keys)'
Expand Down
6 changes: 5 additions & 1 deletion src/app/ui/TitleScene.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ class TitleScene extends React.Component {
<img src={require('./images/logo-with-shadow.svg')} />
</div>
<div className='TitleSceneのenter'>
<a href='javascript://' onClick={this.enterGame}>
<a
href='javascript://'
onClick={this.enterGame}
data-testid='enter-game'
>
Enter Game
</a>
</div>
Expand Down
97 changes: 97 additions & 0 deletions src/devtools/BemuseTestMode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
let _enabled = false

let _lifecycleHandler = {
/** @returns {Promise<void>} */
pauseAt(t) {
throw new Error('Cannot pause: No lifecycle handler registered!')
},
/** @returns {void} */
unpause() {
throw new Error('Cannot unpause: No lifecycle handler registered!')
},
/** @returns {number} */
getScore() {
throw new Error('Cannot get score: No lifecycle handler registered!')
},
}

/**
* Activates the test mode. In this mode,
*
* - An overlay will be displayed indicating the test mode.
* - The following APIs will be available to facilitate automated testing:
* - `BemuseTestMode.pauseAt(t)` will make the game pause at time `t`.
* - `BemuseTestMode.unpause()` will unpause the game.
* - Score submission is disabled.
*
* Note: Once test mode is activated, it cannot be deactivated for the rest of the game session.
*/
export function enableTestMode() {
if (!_enabled) {
_enabled = true
console.log('[Bemuse test mode enabled]')
const overlay = document.createElement('div')
overlay.setAttribute(
'style',
`
position: fixed;
top: 20px;
left: 20px;
font: 20px Comic Sans MS, sans-serif;
z-index: 99999;
background: rgba(0,0,0,0.5);
color: #0f0;
border: 2px solid #0f0;
padding: 4px;
pointer-events: none;
`
)
overlay.innerHTML = `
<strong>Test mode:</strong>
Bemuse is being controlled by automated test software.
`
document.body.appendChild(overlay)
}
}

/**
* Registers a pause handler.
* This allows the `pauseAt` and `unpause` APIs to be used.
*
* @param {typeof _lifecycleHandler} handler
*/
export function setGameLifecycleHandler(handler) {
console.log('[Bemuse test mode] A pause handler has been registered.')
_lifecycleHandler = handler
}

/**
* Returns `true` if test mode is enabled, `false` otherwise.
*/
export function isTestModeEnabled() {
return !!_enabled
}

/**
* Will schedule the game to pause at time `t`.
*
* @param {number} t The song time to pause in seconds
* @returns {Promise<void>} A promise that will be resolved when the time is reached and game is paused.
*/
export function pauseAt(t) {
return _lifecycleHandler.pauseAt(t)
}

/**
* Unpauses the game.
*/
export function unpause() {
return _lifecycleHandler.unpause()
}

/**
* Returns the current score.
*/
export function getScore() {
return _lifecycleHandler.getScore()
}
Loading