Skip to content

Commit

Permalink
Merge pull request #503 from bemusic/e2e-testing
Browse files Browse the repository at this point in the history
Playing around with end-to-end testing
  • Loading branch information
dtinth committed Dec 9, 2018
2 parents ae2b6f8 + 5c6241e commit ca231f5
Show file tree
Hide file tree
Showing 22 changed files with 1,058 additions and 67 deletions.
39 changes: 39 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,42 @@ jobs:
else
echo 'Not a release commit, skipped!'
fi
- persist_to_workspace:
root: .
paths:
- dist
- node_modules
- e2e/node_modules
e2e:
working_directory: ~/bemuse
environment:
FORCE_COLOR: '1'
SCREENSHOT_DIR: /tmp/bemuse-e2e-screenshot
docker:
- image: circleci/node:8-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Install HTTP server
command: |
sudo npm i -g http-server
- run:
name: Run server
command: |
http-server -p 8089 dist
background: true
- run:
name: E2E
command: |
cd e2e
mkdir -p "$SCREENSHOT_DIR"
env BEMUSE_PORT=8089 yarn prescript tests/Gameplay-test.js
no_output_timeout: 1m
- store_artifacts:
path: /tmp/bemuse-e2e-screenshot
destination: screenshots
workflows:
version: 2
tests:
Expand All @@ -117,3 +153,6 @@ workflows:
- test
- test_packages
- build
- e2e:
requires:
- build
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"]
}
9 changes: 9 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "bemuse-e2e",
"version": "0.0.1",
"dependencies": {
"expect": "^23.6.0",
"prescript": "^0.5555.55",
"puppeteer": "^1.11.0"
}
}
109 changes: 109 additions & 0 deletions e2e/tests/Gameplay-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const { action, defer, to } = require('prescript')
const puppeteer = require('puppeteer')
const expect = require('expect')

action('Open browser', async state => {
const puppeteerOptions = { headless: true }
if (process.env.CIRCLECI) {
puppeteerOptions.args = ['--no-sandbox', '--disable-setuid-sandbox']
}
state.browser = await puppeteer.launch(puppeteerOptions)
state.page = await state.browser.newPage()
await state.page.setViewport({ width: 1200, height: 480 })
const testMusicServer =
'https://raw.githubusercontent.com/bemusic/bemuse-test-server/master'
const bemusePort = process.env.BEMUSE_PORT || '8080'
const url = `http://localhost:${bemusePort}/?server=${testMusicServer}`
await state.page.goto(url)
})

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()
)
expect(score).toEqual(event.expectedScore)
})
}

action(
'Let the game finish and wait for result screen to show',
async state => {
await takeScreenshot(state.page, 'gameplay')
await state.page.evaluate(() => {
window.BemuseTestMode.unpause()
})
await state.page.waitForSelector('.ResultScene')
await takeScreenshot(state.page, 'result')
}
)
})

async function takeScreenshot(page, name) {
if (!process.env.SCREENSHOT_DIR) return
await page.screenshot({
path: `${process.env.SCREENSHOT_DIR}/${name}.png`,
})
}
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
30 changes: 17 additions & 13 deletions src/app/ui/MusicChartSelectorItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,29 @@ class MusicChartSelectorItem extends React.Component {
'is-5keys': this.props.chart.keys === '5K',
})
return (
<li className={classes} onClick={this.handleClick}>
{this.props.isTutorial ? (
this.props.chart.keys === '5K' ? (
'Start Tutorial (5 keys)'
) : (
'Start Tutorial (7 keys)'
)
) : (
<span className='MusicChartSelectorItemのlevel'>
{this.props.chart.info.level}
</span>
)}
<li
className={classes}
onClick={this.handleClick}
data-testid={this.props.isSelected ? 'play-selected-chart' : undefined}
>
{this.renderText()}
<span className='MusicChartSelectorItemのplay'>
<Icon name='play' />
</span>
</li>
)
}

renderText() {
if (this.props.isTutorial) {
const gameMode = this.props.chart.keys === '5K' ? '5 keys' : '7 keys'
return `Start Tutorial (${gameMode})`
}
return (
<span className='MusicChartSelectorItemのlevel'>
{this.props.chart.info.level}
</span>
)
}
handleClick = () => {
this.props.onChartClick(this.props.chart)
}
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
Loading

0 comments on commit ca231f5

Please sign in to comment.