tests: Migrate E2E tests to Playwright.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2021-10-22 15:13:57 -07:00
parent 377f08ad5d
commit f4479dfda4
6 changed files with 200 additions and 1154 deletions

1229
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -176,10 +176,11 @@
"electron-notarize": "^1.0.0",
"eslint-import-resolver-typescript": "^2.4.0",
"htmlhint": "^0.15.1",
"medium": "^1.2.0",
"playwright-core": "^1.16.3",
"pre-commit": "^1.2.2",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"spectron": "^15.0.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",

View File

@@ -1,4 +1,5 @@
"use strict";
const {chan, put, take} = require("medium");
const test = require("tape");
const setup = require("./setup.js");
@@ -6,13 +7,18 @@ const setup = require("./setup.js");
test("app runs", async (t) => {
t.timeoutAfter(10e3);
setup.resetTestDataDir();
const app = setup.createApp();
const app = await setup.createApp();
try {
await setup.waitForLoad(app, t);
await app.client.windowByIndex(1); // Focus on webview
await (await app.client.$('//*[@id="connect"]')).waitForExist(); // Id of the connect button
await setup.endTest(app, t);
} catch (error) {
await setup.endTest(app, t, error || "error");
const windows = chan();
for (const win of app.windows()) put(windows, win);
app.on("window", (win) => put(windows, win));
const mainWindow = await take(windows);
t.equal(await mainWindow.title(), "Zulip");
const mainWebview = await take(windows);
await mainWebview.waitForSelector("#connect");
} finally {
await setup.endTest(app);
}
});

View File

@@ -1,66 +1,28 @@
"use strict";
const path = require("path");
const {_electron} = require("playwright-core");
const rimraf = require("rimraf");
const {Application} = require("spectron");
const testsPkg = require("./package.json");
module.exports = {
createApp,
endTest,
waitForLoad,
wait,
resetTestDataDir,
};
// Runs Zulip Desktop.
// Returns a promise that resolves to a Spectron Application once the app has loaded.
// Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly.
// Returns a promise that resolves to an Electron Application once the app has loaded.
function createApp() {
return new Application({
path: path.join(
__dirname,
"..",
"node_modules",
".bin",
"electron" + (process.platform === "win32" ? ".cmd" : ""),
),
return _electron.launch({
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
env: {NODE_ENV: "test"},
waitTimeout: 10e3,
});
}
// Starts the app, waits for it to load, returns a promise
async function waitForLoad(app, t, options) {
if (!options) {
options = {};
}
await app.start();
await app.client.waitUntilWindowLoaded();
await app.client.pause(2000);
const title = await app.webContents.getTitle();
t.equal(title, "Zulip", "html title");
}
// Returns a promise that resolves after 'ms' milliseconds. Default: 1 second
async function wait(ms) {
if (ms === undefined) {
ms = 1000;
} // Default: wait long enough for the UI to update
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// Quit the app, end the test, either in success (!err) or failure (err)
async function endTest(app, t, error) {
await app.client.windowByIndex(0);
await app.stop();
t.end(error);
// Quit the app, end the test
async function endTest(app) {
await app.close();
}
function getAppDataDir() {

View File

@@ -1,4 +1,5 @@
"use strict";
const {chan, put, take} = require("medium");
const test = require("tape");
const setup = require("./setup.js");
@@ -6,20 +7,22 @@ const setup = require("./setup.js");
test("add-organization", async (t) => {
t.timeoutAfter(50e3);
setup.resetTestDataDir();
const app = setup.createApp();
const app = await setup.createApp();
try {
await setup.waitForLoad(app, t);
await app.client.windowByIndex(1); // Focus on webview
await (
await app.client.$(".setting-input-value")
).setValue("chat.zulip.org");
await (await app.client.$("#connect")).click();
await setup.wait(5000);
await app.client.windowByIndex(0); // Switch focus back to main win
await app.client.windowByIndex(1); // Switch focus back to org webview
await (await app.client.$('//*[@id="id_username"]')).waitForExist();
await setup.endTest(app, t);
} catch (error) {
await setup.endTest(app, t, error || "error");
const windows = chan();
for (const win of app.windows()) put(windows, win);
app.on("window", (win) => put(windows, win));
const mainWindow = await take(windows);
t.equal(await mainWindow.title(), "Zulip");
const mainWebview = await take(windows);
await mainWebview.fill(".setting-input-value", "chat.zulip.org");
await mainWebview.click("#connect");
const orgWebview = await take(windows);
await orgWebview.waitForSelector("#id_username");
} finally {
await setup.endTest(app);
}
});

View File

@@ -1,4 +1,5 @@
"use strict";
const {chan, put, take} = require("medium");
const test = require("tape");
const setup = require("./setup.js");
@@ -8,14 +9,18 @@ const setup = require("./setup.js");
test("new-org-link", async (t) => {
t.timeoutAfter(50e3);
setup.resetTestDataDir();
const app = setup.createApp();
const app = await setup.createApp();
try {
await setup.waitForLoad(app, t);
await app.client.windowByIndex(1); // Focus on webview
await (await app.client.$("#open-create-org-link")).click(); // Click on new org link button
await setup.wait(5000);
await setup.endTest(app, t);
} catch (error) {
await setup.endTest(app, t, error || "error");
const windows = chan();
for (const win of app.windows()) put(windows, win);
app.on("window", (win) => put(windows, win));
const mainWindow = await take(windows);
t.equal(await mainWindow.title(), "Zulip");
const mainWebview = await take(windows);
await mainWebview.click("#open-create-org-link");
} finally {
await setup.endTest(app);
}
});