Skip to content

Switch from Chromeless to Playwright for tests #597

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

Merged
merged 6 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
language: node_js
dist: trusty
addons:
chrome: stable
node_js:
- 8
- 10
- node
env:
- NODE_ENV=production
before_install:
- google-chrome-stable --headless --no-sandbox --remote-debugging-port=9222 &
install:
- npm --production=false install
- npm --production=false update
Expand All @@ -24,7 +20,7 @@ jobs:
- npm run docs
- npm run tap
- stage: deploy
node_js: 8
node_js: 10
script: npm run build
before_deploy:
- VPKG=$($(npm bin)/json -f package.json version)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
"babel-loader": "^7.1.4",
"babel-polyfill": "^6.22.0",
"babel-preset-env": "^1.6.1",
"chromeless": "^1.5.1",
"copy-webpack-plugin": "^4.5.1",
"docdash": "^0.4.0",
"eslint": "^4.6.1",
"eslint-config-scratch": "^5.0.0",
"gh-pages": "^1.0.0",
"jsdoc": "^3.5.5",
"json": "^9.0.4",
"playwright-chromium": "^1.0.1",
"scratch-vm": "0.2.0-prerelease.20191227164934",
"tap": "^11.0.0",
"travis-after-all": "^1.4.4",
Expand Down
54 changes: 54 additions & 0 deletions test/helper/page-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* global window, VirtualMachine, ScratchStorage, ScratchSVGRenderer */
/* eslint-env browser */

// Wait for all SVG skins to be loaded.
// TODO: this is extremely janky and should be removed once vm.loadProject waits for SVG skins to load
// https://github.com/LLK/scratch-render/issues/563
window.waitForSVGSkinLoad = renderer => new Promise(resolve => {
// eslint-disable-next-line prefer-const
let interval;

const waitInner = () => {
let numSVGSkins = 0;
let numLoadedSVGSkins = 0;
for (const skin of renderer._allSkins) {
if (skin.constructor.name !== 'SVGSkin') continue;
numSVGSkins++;
if (skin._svgRenderer.loaded) numLoadedSVGSkins++;
}

if (numSVGSkins === numLoadedSVGSkins) {
clearInterval(interval);
resolve();
}
};

interval = setInterval(waitInner, 1);
});

window.loadFileInputIntoVM = (fileInput, vm, render) => {
const reader = new FileReader();
return new Promise(resolve => {
reader.onload = () => {
vm.start();
vm.loadProject(reader.result)
.then(() => window.waitForSVGSkinLoad(render))
.then(() => {
resolve();
});
};
reader.readAsArrayBuffer(fileInput.files[0]);
});
};

window.initVM = render => {
const vm = new VirtualMachine();
const storage = new ScratchStorage();

vm.attachStorage(storage);
vm.attachRenderer(render);
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());

return vm;
};
43 changes: 12 additions & 31 deletions test/integration/cpu-render.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<script src="../../node_modules/scratch-vm/dist/web/scratch-vm.js"></script>
<script src="../../node_modules/scratch-storage/dist/web/scratch-storage.js"></script>
<script src="../../node_modules/scratch-svg-renderer/dist/web/scratch-svg-renderer.js"></script>
<script src="../helper/page-util.js"></script>
<!-- note: this uses the BUILT version of scratch-render! make sure to npm run build -->
<script src="../../dist/web/scratch-render.js"></script>

Expand All @@ -17,38 +18,18 @@
window.devicePixelRatio = 1;
const gpuCanvas = document.getElementById('test');
var render = new ScratchRender(gpuCanvas);
var vm = new VirtualMachine();
var storage = new ScratchStorage();
var vm = initVM(render);

vm.attachStorage(storage);
vm.attachRenderer(render);
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());

document.getElementById('file').addEventListener('click', e => {
document.body.removeChild(document.getElementById('loaded'));
});

document.getElementById('file').addEventListener('change', e => {
const reader = new FileReader();
const thisFileInput = e.target;
reader.onload = () => {
vm.start();
vm.loadProject(reader.result)
.then(() => {
// we add a `#loaded` div to our document, the integration suite
// waits for that element to show up to assume the vm is ready
// to play!
const div = document.createElement('div');
div.id='loaded';
document.body.appendChild(div);
vm.greenFlag();
setTimeout(() => {
renderCpu();
}, 1000);
});
};
reader.readAsArrayBuffer(thisFileInput.files[0]);
const fileInput = document.getElementById('file');
const loadFile = loadFileInputIntoVM.bind(null, fileInput, vm, render);
fileInput.addEventListener('change', e => {
loadFile()
.then(() => {
vm.greenFlag();
setTimeout(() => {
renderCpu();
}, 1000);
});
});

const cpuCanvas = document.getElementById('cpu');
Expand Down
31 changes: 3 additions & 28 deletions test/integration/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<script src="../../node_modules/scratch-vm/dist/web/scratch-vm.js"></script>
<script src="../../node_modules/scratch-storage/dist/web/scratch-storage.js"></script>
<script src="../../node_modules/scratch-svg-renderer/dist/web/scratch-svg-renderer.js"></script>
<script src="../helper/page-util.js"></script>
<!-- note: this uses the BUILT version of scratch-render! make sure to npm run build -->
<script src="../../dist/web/scratch-render.js"></script>

Expand All @@ -15,39 +16,13 @@

var canvas = document.getElementById('test');
var render = new ScratchRender(canvas);
var vm = new VirtualMachine();
var storage = new ScratchStorage();
var vm = initVM(render);
var mockMouse = data => vm.runtime.postIOData('mouse', {
canvasWidth: canvas.width,
canvasHeight: canvas.height,
...data,
});

vm.attachStorage(storage);
vm.attachRenderer(render);
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());

document.getElementById('file').addEventListener('click', e => {
document.body.removeChild(document.getElementById('loaded'));
});

document.getElementById('file').addEventListener('change', e => {
const reader = new FileReader();
const thisFileInput = e.target;
reader.onload = () => {
vm.start();
vm.loadProject(reader.result)
.then(() => {
// we add a `#loaded` div to our document, the integration suite
// waits for that element to show up to assume the vm is ready
// to play!
const div = document.createElement('div');
div.id='loaded';
document.body.appendChild(div);
});
};
reader.readAsArrayBuffer(thisFileInput.files[0]);
});
const loadFile = loadFileInputIntoVM.bind(null, document.getElementById('file'), vm, render);
</script>
</body>
29 changes: 17 additions & 12 deletions test/integration/pick-tests.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
/* global vm, render, Promise */
const {Chromeless} = require('chromeless');
const {chromium} = require('playwright-chromium');
const test = require('tap').test;
const path = require('path');
const chromeless = new Chromeless();

const indexHTML = path.resolve(__dirname, 'index.html');
const testDir = (...args) => path.resolve(__dirname, 'pick-tests', ...args);

const runFile = (file, action, script) =>
const runFile = async (file, action, page, script) => {
// start each test by going to the index.html, and loading the scratch file
chromeless.goto(`file://${indexHTML}`)
.setFileInput('#file', testDir(file))
// the index.html handler for file input will add a #loaded element when it
// finishes.
.wait('#loaded')
.evaluate(`function () {return (${script})(${action});}`)
;
await page.goto(`file://${indexHTML}`);
const fileInput = await page.$('#file');
await fileInput.setInputFiles(testDir(file));

await page.evaluate(() =>
// `loadFile` is defined on the page itself.
// eslint-disable-next-line no-undef
loadFile()
);
return page.evaluate(`(function () {return (${script})(${action});})()`);
};

// immediately invoked async function to let us wait for each test to finish before starting the next.
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();

const testOperation = async function (name, action, expect) {
await test(name, async t => {

const results = await runFile('test-mouse-touch.sb2', action, boundAction => {
const results = await runFile('test-mouse-touch.sb2', action, page, boundAction => {
vm.greenFlag();
const sendResults = [];

Expand Down Expand Up @@ -97,5 +102,5 @@ const runFile = (file, action, script) =>
}

// close the browser window we used
await chromeless.end();
await browser.close();
})();
87 changes: 46 additions & 41 deletions test/integration/scratch-tests.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,56 @@
/* global vm, Promise */
const {Chromeless} = require('chromeless');
const {chromium} = require('playwright-chromium');
const test = require('tap').test;
const path = require('path');
const fs = require('fs');
const chromeless = new Chromeless();

const indexHTML = path.resolve(__dirname, 'index.html');
const testDir = (...args) => path.resolve(__dirname, 'scratch-tests', ...args);

const testFile = file => test(file, async t => {
const testFile = (file, page) => test(file, async t => {
// start each test by going to the index.html, and loading the scratch file
const says = await chromeless.goto(`file://${indexHTML}`)
.setFileInput('#file', testDir(file))
// the index.html handler for file input will add a #loaded element when it
// finishes.
.wait('#loaded')
.evaluate(() => {
// This function is run INSIDE the integration chrome browser via some
// injection and .toString() magic. We can return some "simple data"
// back across as a promise, so we will just log all the says that happen
// for parsing after.

// this becomes the `says` in the outer scope
const messages = [];
const TIMEOUT = 5000;

vm.runtime.on('SAY', (_, __, message) => {
messages.push(message);
});
await page.goto(`file://${indexHTML}`);
const fileInput = await page.$('#file');
await fileInput.setInputFiles(testDir(file));
await page.evaluate(() =>
// `loadFile` is defined on the page itself.
// eslint-disable-next-line no-undef
loadFile()
);
const says = await page.evaluate(() => {
// This function is run INSIDE the integration chrome browser via some
// injection and .toString() magic. We can return some "simple data"
// back across as a promise, so we will just log all the says that happen
// for parsing after.

// this becomes the `says` in the outer scope
const messages = [];
const TIMEOUT = 5000;

vm.runtime.on('SAY', (_, __, message) => {
messages.push(message);
});

vm.greenFlag();
const startTime = Date.now();

return Promise.resolve()
.then(async () => {
// waiting for all threads to complete, then we return
while (vm.runtime.threads.some(thread => vm.runtime.isActiveThread(thread))) {
if ((Date.now() - startTime) >= TIMEOUT) {
// if we push the message after end, the failure from tap is not very useful:
// "not ok test after end() was called"
messages.unshift(`fail Threads still running after ${TIMEOUT}ms`);
break;
}

await new Promise(resolve => setTimeout(resolve, 50));
vm.greenFlag();
const startTime = Date.now();

return Promise.resolve()
.then(async () => {
// waiting for all threads to complete, then we return
while (vm.runtime.threads.some(thread => vm.runtime.isActiveThread(thread))) {
if ((Date.now() - startTime) >= TIMEOUT) {
// if we push the message after end, the failure from tap is not very useful:
// "not ok test after end() was called"
messages.unshift(`fail Threads still running after ${TIMEOUT}ms`);
break;
}

return messages;
});
});
await new Promise(resolve => setTimeout(resolve, 50));
}

return messages;
});
});

// Map string messages to tap reporting methods. This will be used
// with events from scratch's runtime emitted on block instructions.
Expand Down Expand Up @@ -103,13 +105,16 @@ const testFile = file => test(file, async t => {

// immediately invoked async function to let us wait for each test to finish before starting the next.
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();

const files = fs.readdirSync(testDir())
.filter(uri => uri.endsWith('.sb2') || uri.endsWith('.sb3'));

for (const file of files) {
await testFile(file);
await testFile(file, page);
}

// close the browser window we used
await chromeless.end();
await browser.close();
})();