mirror of
https://github.com/discourse/discourse.git
synced 2026-09-05 04:40:41 -05:00
DEV: Clean up smoke-test (#40066)
* don't use `path` for making urls * remove an unused `assertion` argument * convert a function to async/await * move import to the top * merge two logger callbacks * remove unnecessary IIFE * `state: visible` is the default, drop those args * const -> function * use `Date.now` * use a string template * use a `Object.entries` loop
This commit is contained in:
+241
-276
@@ -1,330 +1,295 @@
|
||||
/* eslint no-console: "off" */
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
import { chromium } from "playwright";
|
||||
|
||||
if (args.length < 1 || args.length > 2) {
|
||||
if (process.argv.length !== 3) {
|
||||
console.log("Expecting: node test/smoke-test.mjs {URL}");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const url = args[0];
|
||||
|
||||
const url = process.argv[2];
|
||||
console.log(`Starting Discourse Smoke Test for ${url}`);
|
||||
|
||||
import path from "path";
|
||||
import { chromium } from "playwright";
|
||||
const browser = await chromium.launch({
|
||||
channel: "chrome",
|
||||
// when debugging locally setting the SHOW_BROWSER env variable can be very helpful
|
||||
headless: process.env.SHOW_BROWSER === undefined,
|
||||
args: ["--no-sandbox"],
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
channel: "chrome",
|
||||
// when debugging locally setting the SHOW_BROWSER env variable can be very helpful
|
||||
headless: process.env.SHOW_BROWSER === undefined,
|
||||
args: ["--no-sandbox"],
|
||||
});
|
||||
const contextOptions = {
|
||||
viewport: { width: 1366, height: 768 },
|
||||
};
|
||||
|
||||
const contextOptions = {
|
||||
viewport: { width: 1366, height: 768 },
|
||||
if (process.env.AUTH_USER && process.env.AUTH_PASSWORD) {
|
||||
contextOptions.httpCredentials = {
|
||||
username: process.env.AUTH_USER,
|
||||
password: process.env.AUTH_PASSWORD,
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.AUTH_USER && process.env.AUTH_PASSWORD) {
|
||||
contextOptions.httpCredentials = {
|
||||
username: process.env.AUTH_USER,
|
||||
password: process.env.AUTH_PASSWORD,
|
||||
};
|
||||
const context = await browser.newContext(contextOptions);
|
||||
const page = await context.newPage();
|
||||
|
||||
page.on("console", (msg) => {
|
||||
console.log(`PAGE ${msg.type().toUpperCase()}: ${msg.text()}`);
|
||||
});
|
||||
page.on("pageerror", (err) => console.log(`PAGE ERROR: ${err.message}`));
|
||||
|
||||
function takeFailureScreenshot() {
|
||||
const screenshotPath = `${
|
||||
process.env.SMOKE_TEST_SCREENSHOT_PATH || "tmp/smoke-test-screenshots"
|
||||
}/smoke-test-${Date.now()}.png`;
|
||||
console.log(`Screenshot of failure taken at ${screenshotPath}`);
|
||||
return page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
}
|
||||
|
||||
const exec = async (description, fn) => {
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
await fn();
|
||||
console.log(`PASSED: ${description} - ${Date.now() - start}ms`);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`ERROR (${description}): ${error.message} - ${Date.now() - start}ms`
|
||||
);
|
||||
await takeFailureScreenshot();
|
||||
console.log("SMOKE TEST FAILED");
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const context = await browser.newContext(contextOptions);
|
||||
const page = await context.newPage();
|
||||
|
||||
page.on("console", (msg) => {
|
||||
if (["error", "warning"].includes(msg.type())) {
|
||||
console.log(`PAGE ${msg.type().toUpperCase()}: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
page.on("pageerror", (err) => console.log(`PAGE ERROR: ${err.message}`));
|
||||
|
||||
const takeFailureScreenshot = function () {
|
||||
const screenshotPath = `${
|
||||
process.env.SMOKE_TEST_SCREENSHOT_PATH || "tmp/smoke-test-screenshots"
|
||||
}/smoke-test-${Date.now()}.png`;
|
||||
console.log(`Screenshot of failure taken at ${screenshotPath}`);
|
||||
return page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
};
|
||||
|
||||
const exec = (description, fn, assertion) => {
|
||||
const start = +new Date();
|
||||
|
||||
return fn
|
||||
.call()
|
||||
.then(async (output) => {
|
||||
if (assertion) {
|
||||
if (assertion.call(this, output)) {
|
||||
console.log(`PASSED: ${description} - ${+new Date() - start}ms`);
|
||||
} else {
|
||||
console.log(`FAILED: ${description} - ${+new Date() - start}ms`);
|
||||
await takeFailureScreenshot();
|
||||
console.log("SMOKE TEST FAILED");
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(`PASSED: ${description} - ${+new Date() - start}ms`);
|
||||
}
|
||||
})
|
||||
.catch(async (error) => {
|
||||
console.log(
|
||||
`ERROR (${description}): ${error.message} - ${+new Date() - start}ms`
|
||||
);
|
||||
await takeFailureScreenshot();
|
||||
console.log("SMOKE TEST FAILED");
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
const assert = (description, fn, assertion) => {
|
||||
return exec(description, fn, assertion);
|
||||
};
|
||||
|
||||
page.on("console", (msg) => console.log(`PAGE LOG: ${msg.text()}`));
|
||||
|
||||
page.on("response", (resp) => {
|
||||
if (![200, 204, 302].includes(resp.status())) {
|
||||
console.log(
|
||||
"FAILED HTTP REQUEST TO " + resp.url() + " Status is: " + resp.status()
|
||||
);
|
||||
if (resp.status() === 429) {
|
||||
const headers = resp.headers();
|
||||
console.log("Response headers:");
|
||||
Object.keys(headers).forEach((key) => {
|
||||
console.log(`${key}: ${headers[key]}`);
|
||||
});
|
||||
page.on("response", (resp) => {
|
||||
if (![200, 204, 302].includes(resp.status())) {
|
||||
console.log(
|
||||
`FAILED HTTP REQUEST TO ${resp.url()} Status is: ${resp.status()}`
|
||||
);
|
||||
if (resp.status() === 429) {
|
||||
const headers = resp.headers();
|
||||
console.log("Response headers:");
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
return resp;
|
||||
});
|
||||
|
||||
async function login() {
|
||||
await exec("open login modal or page", () => {
|
||||
return page.click(".login-button");
|
||||
});
|
||||
|
||||
const login = async function () {
|
||||
await exec("open login modal or page", () => {
|
||||
return page.click(".login-button");
|
||||
});
|
||||
|
||||
await exec("login form is visible", () => {
|
||||
return page.waitForSelector("#login-form", { state: "visible" });
|
||||
});
|
||||
|
||||
await exec("type in credentials & log in", async () => {
|
||||
await page
|
||||
.locator("#login-account-name")
|
||||
.pressSequentially(process.env.DISCOURSE_USERNAME || "smoke_user");
|
||||
|
||||
await page
|
||||
.locator("#login-account-password")
|
||||
.pressSequentially(process.env.DISCOURSE_PASSWORD || "P4ssw0rd");
|
||||
|
||||
return page.click("#login-button");
|
||||
});
|
||||
|
||||
await exec("is logged in", () => {
|
||||
return page.waitForSelector(".current-user", { state: "visible" });
|
||||
});
|
||||
};
|
||||
|
||||
await exec("go to site", () => {
|
||||
return page.goto(url);
|
||||
await exec("login form is visible", () => {
|
||||
return page.waitForSelector("#login-form");
|
||||
});
|
||||
|
||||
await exec("expect a log in button in the header", () => {
|
||||
return page.waitForSelector("header .login-button", { state: "visible" });
|
||||
await exec("type in credentials & log in", async () => {
|
||||
await page
|
||||
.locator("#login-account-name")
|
||||
.pressSequentially(process.env.DISCOURSE_USERNAME || "smoke_user");
|
||||
|
||||
await page
|
||||
.locator("#login-account-password")
|
||||
.pressSequentially(process.env.DISCOURSE_PASSWORD || "P4ssw0rd");
|
||||
|
||||
return page.click("#login-button");
|
||||
});
|
||||
|
||||
if (process.env.LOGIN_AT_BEGINNING) {
|
||||
await exec("is logged in", () => {
|
||||
return page.waitForSelector(".current-user");
|
||||
});
|
||||
}
|
||||
|
||||
await exec("go to site", () => {
|
||||
return page.goto(url);
|
||||
});
|
||||
|
||||
await exec("expect a log in button in the header", () => {
|
||||
return page.waitForSelector("header .login-button");
|
||||
});
|
||||
|
||||
if (process.env.LOGIN_AT_BEGINNING) {
|
||||
await login();
|
||||
}
|
||||
|
||||
await exec("go to latest page", () => {
|
||||
return page.goto(new URL("latest", url).href);
|
||||
});
|
||||
|
||||
await exec("at least one topic shows up", () => {
|
||||
return page.waitForSelector(".topic-list tbody tr");
|
||||
});
|
||||
|
||||
await exec("go to categories page", () => {
|
||||
return page.goto(new URL("categories", url).href);
|
||||
});
|
||||
|
||||
await exec("can see categories on the page", () => {
|
||||
return page.waitForSelector(".category-list");
|
||||
});
|
||||
|
||||
await exec("navigate to 1st topic", () => {
|
||||
return page.click(".main-link a.title:first-of-type");
|
||||
});
|
||||
|
||||
await exec("at least one post body", () => {
|
||||
return page.waitForSelector(".topic-post");
|
||||
});
|
||||
|
||||
await exec("click on the 1st user", () => {
|
||||
return page.click(".topic-meta-data a:first-of-type");
|
||||
});
|
||||
|
||||
await exec("user has details", () => {
|
||||
return page.waitForSelector(".user-card .names");
|
||||
});
|
||||
|
||||
if (!process.env.READONLY_TESTS) {
|
||||
if (!process.env.LOGIN_AT_BEGINNING) {
|
||||
await login();
|
||||
}
|
||||
|
||||
await exec("go to latest page", () => {
|
||||
return page.goto(path.join(url, "latest"));
|
||||
await exec("go home", async () => {
|
||||
await page.waitForSelector("#site-logo, #site-text-logo");
|
||||
|
||||
return page.click("#site-logo, #site-text-logo");
|
||||
});
|
||||
|
||||
await exec("at least one topic shows up", () => {
|
||||
return page.waitForSelector(".topic-list tbody tr", { state: "visible" });
|
||||
await exec("it shows a topic list", () => {
|
||||
return page.waitForSelector(".topic-list");
|
||||
});
|
||||
|
||||
await exec("go to categories page", () => {
|
||||
return page.goto(path.join(url, "categories"));
|
||||
await exec("we have a create topic button", () => {
|
||||
return page.waitForSelector("#create-topic");
|
||||
});
|
||||
|
||||
await exec("can see categories on the page", () => {
|
||||
return page.waitForSelector(".category-list", { state: "visible" });
|
||||
await exec("open composer", () => {
|
||||
return page.click("#create-topic");
|
||||
});
|
||||
|
||||
await exec("navigate to 1st topic", () => {
|
||||
return page.click(".main-link a.title:first-of-type");
|
||||
await exec("the editor is visible", () => {
|
||||
return page.locator("#reply-title:focus").waitFor();
|
||||
});
|
||||
|
||||
await exec("at least one post body", () => {
|
||||
return page.waitForSelector(".topic-post", { state: "visible" });
|
||||
await page.evaluate(() => {
|
||||
document.getElementById("reply-title").value = "";
|
||||
});
|
||||
|
||||
await exec("click on the 1st user", () => {
|
||||
return page.click(".topic-meta-data a:first-of-type");
|
||||
await exec("composer is open", () => {
|
||||
return page.waitForSelector("#reply-control .d-editor-input");
|
||||
});
|
||||
|
||||
await exec("user has details", () => {
|
||||
return page.waitForSelector(".user-card .names", { state: "visible" });
|
||||
await exec("compose new topic", async () => {
|
||||
const date = `(${Date.now()})`;
|
||||
const title = `This is a new topic ${date}`;
|
||||
const post = `I can write a new topic inside the smoke test! ${date} \n\n`;
|
||||
|
||||
await page.locator("#reply-title").pressSequentially(title);
|
||||
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially(post);
|
||||
});
|
||||
|
||||
if (!process.env.READONLY_TESTS) {
|
||||
if (!process.env.LOGIN_AT_BEGINNING) {
|
||||
await login();
|
||||
}
|
||||
// await exec("updates preview", () => {
|
||||
// return page.waitForSelector(".d-editor-preview p");
|
||||
// });
|
||||
|
||||
await exec("go home", async () => {
|
||||
await page.waitForSelector("#site-logo, #site-text-logo", {
|
||||
state: "visible",
|
||||
});
|
||||
await exec("submit the topic", () => {
|
||||
return page.click(".submit-panel .create");
|
||||
});
|
||||
|
||||
return page.click("#site-logo, #site-text-logo");
|
||||
await exec("topic is created", () => {
|
||||
return page.waitForSelector(".fancy-title");
|
||||
});
|
||||
|
||||
await exec("open the composer", () => {
|
||||
return page.click(".post-controls:first-of-type .create");
|
||||
});
|
||||
|
||||
await exec("composer is open", () => {
|
||||
return page.waitForSelector("#reply-control .d-editor-input");
|
||||
});
|
||||
|
||||
await exec("compose reply", () => {
|
||||
const post = `I can even write a reply inside the smoke test ;) (${Date.now()})`;
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially(post);
|
||||
});
|
||||
|
||||
// await exec("waiting for the preview", async () => {
|
||||
// await page.waitForSelector("div.d-editor-preview", {
|
||||
// state: "visible",
|
||||
// });
|
||||
// return page.waitForFunction(
|
||||
// "document.querySelector('div.d-editor-preview').innerText.includes('I can even write a reply')"
|
||||
// );
|
||||
// });
|
||||
|
||||
await exec("wait a little bit", () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
await exec("submit the reply", async () => {
|
||||
await page.click("#reply-control .create");
|
||||
|
||||
return page.waitForSelector("#reply-control.closed", {
|
||||
state: "attached",
|
||||
});
|
||||
});
|
||||
|
||||
await exec("it shows a topic list", () => {
|
||||
return page.waitForSelector(".topic-list", { state: "visible" });
|
||||
});
|
||||
await exec("reply is created", () => {
|
||||
return page
|
||||
.locator(".topic-post:not(.staged) #post_2 .cooked", {
|
||||
hasText: "I can even write a reply",
|
||||
})
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
await exec("we have a create topic button", () => {
|
||||
return page.waitForSelector("#create-topic", { state: "visible" });
|
||||
});
|
||||
|
||||
await exec("open composer", () => {
|
||||
return page.click("#create-topic");
|
||||
});
|
||||
|
||||
await exec("the editor is visible", () => {
|
||||
return page.locator("#reply-title:focus").waitFor();
|
||||
});
|
||||
await exec("wait a little bit", () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
await exec("open composer to edit first post", async () => {
|
||||
await page.evaluate(() => {
|
||||
document.getElementById("reply-title").value = "";
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
await exec("composer is open", () => {
|
||||
return page.waitForSelector("#reply-control .d-editor-input", {
|
||||
state: "visible",
|
||||
});
|
||||
});
|
||||
await page.click("#post_1 .post-controls .edit");
|
||||
|
||||
await exec("compose new topic", async () => {
|
||||
const date = `(${+new Date()})`;
|
||||
const title = `This is a new topic ${date}`;
|
||||
const post = `I can write a new topic inside the smoke test! ${date} \n\n`;
|
||||
|
||||
await page.locator("#reply-title").pressSequentially(title);
|
||||
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially(post);
|
||||
});
|
||||
|
||||
// await exec("updates preview", () => {
|
||||
// return page.waitForSelector(".d-editor-preview p", { state: "visible" });
|
||||
// });
|
||||
|
||||
await exec("submit the topic", () => {
|
||||
return page.click(".submit-panel .create");
|
||||
});
|
||||
|
||||
await exec("topic is created", () => {
|
||||
return page.waitForSelector(".fancy-title", { state: "visible" });
|
||||
});
|
||||
|
||||
await exec("open the composer", () => {
|
||||
return page.click(".post-controls:first-of-type .create");
|
||||
});
|
||||
|
||||
await exec("composer is open", () => {
|
||||
return page.waitForSelector("#reply-control .d-editor-input", {
|
||||
state: "visible",
|
||||
});
|
||||
});
|
||||
|
||||
await exec("compose reply", () => {
|
||||
const post = `I can even write a reply inside the smoke test ;) (${+new Date()})`;
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially(post);
|
||||
});
|
||||
|
||||
// await exec("waiting for the preview", async () => {
|
||||
// await page.waitForSelector("div.d-editor-preview", {
|
||||
// state: "visible",
|
||||
// });
|
||||
// return page.waitForFunction(
|
||||
// "document.querySelector('div.d-editor-preview').innerText.includes('I can even write a reply')"
|
||||
// );
|
||||
// });
|
||||
|
||||
await exec("wait a little bit", () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
await exec("submit the reply", async () => {
|
||||
await page.click("#reply-control .create");
|
||||
|
||||
return page.waitForSelector("#reply-control.closed", {
|
||||
state: "attached",
|
||||
});
|
||||
});
|
||||
|
||||
await assert("reply is created", () => {
|
||||
return page
|
||||
.locator(".topic-post:not(.staged) #post_2 .cooked", {
|
||||
hasText: "I can even write a reply",
|
||||
})
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
await exec("wait a little bit", () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
await exec("open composer to edit first post", async () => {
|
||||
await page.evaluate(() => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
await page.click("#post_1 .post-controls .edit");
|
||||
|
||||
return page.waitForSelector("#reply-control .d-editor-input", {
|
||||
state: "visible",
|
||||
});
|
||||
});
|
||||
|
||||
await exec("update post raw in composer", async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially("\n\nI edited this post");
|
||||
});
|
||||
|
||||
await exec("submit the edit", async () => {
|
||||
await page.click("#reply-control .create");
|
||||
|
||||
return page.waitForSelector("#reply-control.closed", {
|
||||
state: "attached",
|
||||
});
|
||||
});
|
||||
|
||||
await assert("edit is successful", () => {
|
||||
return page
|
||||
.locator(".topic-post:not(.staged) #post_1 .cooked", {
|
||||
hasText: "I edited this post",
|
||||
})
|
||||
.waitFor();
|
||||
});
|
||||
}
|
||||
|
||||
await exec("close browser", () => {
|
||||
return browser.close();
|
||||
return page.waitForSelector("#reply-control .d-editor-input");
|
||||
});
|
||||
|
||||
console.log("ALL PASSED");
|
||||
})();
|
||||
await exec("update post raw in composer", async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
return page
|
||||
.locator("#reply-control .d-editor-input")
|
||||
.pressSequentially("\n\nI edited this post");
|
||||
});
|
||||
|
||||
await exec("submit the edit", async () => {
|
||||
await page.click("#reply-control .create");
|
||||
|
||||
return page.waitForSelector("#reply-control.closed", {
|
||||
state: "attached",
|
||||
});
|
||||
});
|
||||
|
||||
await exec("edit is successful", () => {
|
||||
return page
|
||||
.locator(".topic-post:not(.staged) #post_1 .cooked", {
|
||||
hasText: "I edited this post",
|
||||
})
|
||||
.waitFor();
|
||||
});
|
||||
}
|
||||
|
||||
await exec("close browser", () => {
|
||||
return browser.close();
|
||||
});
|
||||
|
||||
console.log("ALL PASSED");
|
||||
|
||||
Reference in New Issue
Block a user