Travis: add scripts to test if headless Chrome runs properly

This commit is contained in:
Alexey Andreev 2019-08-20 18:51:12 +03:00
parent 492fd004af
commit 0a5ed2b4a5
8 changed files with 294 additions and 4 deletions

View File

@ -39,6 +39,11 @@ install:
- npm config set prefix=$HOME/.node_modules
- npm install
- npm run build
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 index.html &
- BROWSER_PID=$!
- node start.js test
- node ./bin/test-chrome.js
- kill $BROWSER_PID
- popd
- rm -rf tools/idea/idea-artifacts/dependencies

View File

@ -20,7 +20,7 @@ function tryConnect() {
let ws = new WebSocket("ws://localhost:9090");
ws.onopen = () => {
console.log("Connected established");
console.log("Connection established");
listen(ws);
};
@ -30,6 +30,10 @@ function tryConnect() {
tryConnect();
}, 500);
};
ws.onerror = err => {
console.log("Could not connect WebSocket", err);
}
}
function listen(ws) {
@ -58,12 +62,14 @@ function runTests(ws, suiteId, tests, index) {
function runSingleTest(test, callback) {
console.log("Running test " + test.name + " consisting of " + test.files);
let iframe = document.getElementById("test");
let iframe = document.createElement("iframe");
document.body.appendChild(iframe);
let handshakeListener = () => {
window.removeEventListener("message", handshakeListener);
let listener = event => {
window.removeEventListener("message", listener);
document.body.removeChild(iframe);
callback(event.data);
};
window.addEventListener("message", listener);

View File

@ -21,6 +21,5 @@
<script src="client.js"></script>
</head>
<body>
<iframe id="test" src="frame.html"></iframe>
</body>
</html>

View File

@ -0,0 +1,118 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
import * as http from "http";
import { client as WebSocketClient } from "websocket";
function httpGetJson(url) {
return new Promise((resolve, reject) => {
http.request(url, resp => {
if (resp.statusCode !== 200) {
reject(new Error(`HTTP status: ${resp.statusCode}`));
return;
}
let data = "";
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
resolve(JSON.parse(data));
});
}).on("error", err => {
reject(new Error(`HTTP error: ${err.message}`));
}).end();
});
}
function connectWs(url) {
return new Promise((resolve, reject) => {
const client = new WebSocketClient();
client.on("connectFailed", error => reject(new Error('Connect Error: ' + error)));
client.on("connect", resolve);
client.connect(url);
});
}
class CDP {
constructor(conn) {
this.idGenerator = 1;
this.pendingCalls = Object.create(null);
this.eventHandlers = Object.create(null);
this.conn = conn;
}
start() {
this.conn.on("message", message => {
if (message.type === 'utf8') {
const messageObj = JSON.parse(message.utf8Data);
if (messageObj.id !== void 0) {
const pendingCall = this.pendingCalls[messageObj.id];
delete this.pendingCalls[messageObj.id];
if (messageObj.error) {
pendingCall.reject(new Error(`Error calling CDP method ${messageObj.error}`));
} else {
pendingCall.resolve(messageObj.result);
}
} else {
const handlers = this.eventHandlers[messageObj.method];
if (handlers) {
for (const handler of handlers) {
handler(messageObj.params);
}
}
}
}
});
this.conn.on("close", () => {
for (const key in Object.getOwnPropertyNames(this.pendingCalls)) {
this.pendingCalls[key].reject(new Error("Connection closed before result received"));
}
});
this.conn.on("error", err => {
console.error("WS error: %j", err);
});
}
call(method, params = undefined) {
return new Promise((resolve, reject) => {
const id = this.idGenerator++;
this.pendingCalls[id] = { resolve, reject };
this.conn.send(JSON.stringify({ id, method, params }));
});
}
async on(eventName, handler) {
let handlers = this.eventHandlers[eventName];
if (handlers === void 0) {
handlers = [];
this.eventHandlers[eventName] = handlers;
}
handlers.push(handler);
}
static async connect(url) {
const targets = await httpGetJson(url + "/json/list");
const wsUrl = targets.find(target => target.type === "page").webSocketDebuggerUrl;
console.log("Connected to Chrome");
const wsConn = await connectWs(wsUrl);
console.log(`Connected to WS endpoint: ${wsUrl}`);
return wsConn;
}
}
export { CDP };

View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
import { CDP } from "./cdp.js";
async function run() {
const wsConn = await CDP.connect("http://localhost:9222");
const cdp = new CDP(wsConn);
const waitForLog = new Promise(resolve => {
let timeout = setTimeout(resolve, 500);
cdp.on("Runtime.consoleAPICalled", event => {
const value = event.args.filter(arg => arg.type === "string").map(arg => arg.value).join("");
if (value !== "") {
console.log("[LOG] " + new Date(event.timestamp) + ": " + value);
}
clearTimeout(timeout);
timeout = setTimeout(resolve, 500);
});
});
cdp.start();
try {
await cdp.call("Runtime.enable");
await waitForLog;
} finally {
wsConn.close();
}
}
run().catch(e => {
console.error("Error", e);
});

View File

@ -192,7 +192,7 @@ class TestRunner {
const resultPromises = [];
this.timeout = createRefreshableTimeoutPromise(10000);
this.timeout = createRefreshableTimeoutPromise(20000);
for (let i = 0; i < suite.testCases.length; ++i) {
resultPromises.push(new Promise(resolve => {
this.pendingRequests[request.id + "-" + i] = resolve;

View File

@ -0,0 +1,95 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
import { CDP } from "./cdp.js";
function waitForTest(cdp) {
return new Promise(resolve => {
let stage = 'initial';
let requestId = null;
cdp.on("Runtime.consoleAPICalled", event => {
const value = event.args.filter(arg => arg.type === "string").map(arg => arg.value).join("");
switch (stage) {
case 'initial':
if (value === 'Connection established') {
stage = 'connected';
}
break;
case 'connected': {
const result = /Request #([0-9]+) received/.exec(value);
if (result) {
requestId = result[1];
stage = 'sent';
}
break;
}
case 'sent':
if (value === "Running test only simple consisting of http://localhost:9090//only/test.js") {
stage = 'ran';
}
break;
case 'ran':
if (value === "Sending response #" + requestId) {
stage = 'received';
resolve();
}
break;
}
});
});
}
function timeout(time) {
let timer;
return {
promise: new Promise((resolve, reject) => {
timer = setTimeout(() => { reject(new Error("Timeout expired")); }, time);
}),
cancel: () => clearTimeout(timer)
}
}
async function run() {
const wsConn = await CDP.connect("http://localhost:9222");
const cdp = new CDP(wsConn);
cdp.on("Runtime.consoleAPICalled", event => {
const value = event.args.filter(arg => arg.type === "string").map(arg => arg.value).join("");
if (value !== "") {
console.log("[LOG] " + new Date(event.timestamp) + ": " + value);
}
});
const wait = waitForTest(cdp);
let timer;
cdp.start();
try {
await cdp.call("Runtime.enable");
timer = timeout(10000);
await Promise.race([wait, timer.promise]);
} finally {
if (timer) {
timer.cancel();
}
wsConn.close();
}
}
run().catch(e => {
console.error("Error", e);
});

View File

@ -0,0 +1,19 @@
/*
* Copyright 2019 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function main(args, callback) {
callback();
}