puppeteer_lib: Explode CommonUtils class.

This may have originally made sense as a class that managed the
browser state, but it has since turned into a dumping ground for
mostly pure functions that don’t make sense to instantiate.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2021-03-26 19:52:16 -07:00
committed by Tim Abbott
parent 53cf974b89
commit 663f1a387d
20 changed files with 588 additions and 584 deletions

View File

@@ -16,14 +16,13 @@ const puppeteer_dir = path.join(root_dir, "var/puppeteer");
type Message = Record<string, string | boolean> & {recipient?: string; content: string}; type Message = Record<string, string | boolean> & {recipient?: string; content: string};
class CommonUtils { let browser: Browser | null = null;
browser: Browser | null = null; let screenshot_id = 0;
screenshot_id = 0; export const is_firefox = process.env.PUPPETEER_PRODUCT === "firefox";
is_firefox = process.env.PUPPETEER_PRODUCT === "firefox"; let realm_url = "http://zulip.zulipdev.com:9981/";
realm_url = "http://zulip.zulipdev.com:9981/"; const gps = new StackTraceGPS({ajax: async (url) => (await fetch(url)).text()});
gps = new StackTraceGPS({ajax: async (url) => (await fetch(url)).text()});
pm_recipient = { export const pm_recipient = {
async set(page: Page, recipient: string): Promise<void> { async set(page: Page, recipient: string): Promise<void> {
// Without using the delay option here there seems to be // Without using the delay option here there seems to be
// a flake where the typeahead doesn't show up. // a flake where the typeahead doesn't show up.
@@ -34,36 +33,32 @@ class CommonUtils {
// meant for something else; e.g., the private message // meant for something else; e.g., the private message
// input typeahead is different from the topic input // input typeahead is different from the topic input
// typeahead but both can be present in the DOM. // typeahead but both can be present in the DOM.
const entry = await page.waitForSelector( const entry = await page.waitForSelector('.typeahead[style*="display: block"] .active a', {
'.typeahead[style*="display: block"] .active a', visible: true,
{visible: true}, });
);
await entry!.click(); await entry!.click();
}, },
async expect(page: Page, expected: string): Promise<void> { async expect(page: Page, expected: string): Promise<void> {
const actual_recipients = await page.evaluate(() => const actual_recipients = await page.evaluate(() => zulip_test.private_message_recipient());
zulip_test.private_message_recipient(),
);
assert.equal(actual_recipients, expected); assert.equal(actual_recipients, expected);
}, },
}; };
fullname: Record<string, string> = { export const fullname: Record<string, string> = {
cordelia: "Cordelia, Lear's daughter", cordelia: "Cordelia, Lear's daughter",
othello: "Othello, the Moor of Venice", othello: "Othello, the Moor of Venice",
hamlet: "King Hamlet", hamlet: "King Hamlet",
}; };
window_size = { export const window_size = {
width: 1400, width: 1400,
height: 1024, height: 1024,
}; };
async ensure_browser(): Promise<Browser> { export async function ensure_browser(): Promise<Browser> {
if (this.browser === null) { if (browser === null) {
const {window_size} = this; browser = await puppeteer.launch({
this.browser = await puppeteer.launch({
args: [ args: [
`--window-size=${window_size.width},${window_size.height}`, `--window-size=${window_size.width},${window_size.height}`,
"--no-sandbox", "--no-sandbox",
@@ -75,28 +70,28 @@ class CommonUtils {
headless: true, headless: true,
}); });
} }
return this.browser; return browser;
} }
async get_page(): Promise<Page> { export async function get_page(): Promise<Page> {
const browser = await this.ensure_browser(); const browser = await ensure_browser();
const page = await browser.newPage(); const page = await browser.newPage();
return page; return page;
} }
async screenshot(page: Page, name: string | null = null): Promise<void> { export async function screenshot(page: Page, name: string | null = null): Promise<void> {
if (name === null) { if (name === null) {
name = `${this.screenshot_id}`; name = `${screenshot_id}`;
this.screenshot_id += 1; screenshot_id += 1;
} }
const screenshot_path = path.join(puppeteer_dir, `${name}.png`); const screenshot_path = path.join(puppeteer_dir, `${name}.png`);
await page.screenshot({ await page.screenshot({
path: screenshot_path, path: screenshot_path,
}); });
} }
async page_url_with_fragment(page: Page): Promise<string> { export async function page_url_with_fragment(page: Page): Promise<string> {
// `page.url()` does not include the url fragment when running // `page.url()` does not include the url fragment when running
// Puppeteer with Firefox: https://github.com/puppeteer/puppeteer/issues/6787. // Puppeteer with Firefox: https://github.com/puppeteer/puppeteer/issues/6787.
// //
@@ -104,17 +99,17 @@ class CommonUtils {
// puppeteer upstream, we can delete this function and return // puppeteer upstream, we can delete this function and return
// its callers to using `page.url()` // its callers to using `page.url()`
return await page.evaluate(() => window.location.href); return await page.evaluate(() => window.location.href);
} }
// This function will clear the existing value of the element and // This function will clear the existing value of the element and
// replace it with the text. // replace it with the text.
async clear_and_type(page: Page, selector: string, text: string): Promise<void> { export async function clear_and_type(page: Page, selector: string, text: string): Promise<void> {
// Select all text currently in the element. // Select all text currently in the element.
await page.click(selector, {clickCount: 3}); await page.click(selector, {clickCount: 3});
await page.type(selector, text); await page.type(selector, text);
} }
/** /**
* This function takes a params object whose fields * This function takes a params object whose fields
* are referenced by name attribute of an input field and * are referenced by name attribute of an input field and
* the input as a key. * the input as a key.
@@ -131,11 +126,11 @@ class CommonUtils {
* terms: true * terms: true
* }); * });
*/ */
async fill_form( export async function fill_form(
page: Page, page: Page,
form_selector: string, form_selector: string,
params: Record<string, boolean | string>, params: Record<string, boolean | string>,
): Promise<void> { ): Promise<void> {
async function is_dropdown(page: Page, name: string): Promise<boolean> { async function is_dropdown(page: Page, name: string): Promise<boolean> {
return (await page.$(`select[name="${name}"]`)) !== null; return (await page.$(`select[name="${name}"]`)) !== null;
} }
@@ -165,13 +160,13 @@ class CommonUtils {
await page.type(name_selector, value); await page.type(name_selector, value);
} }
} }
} }
async check_form_contents( export async function check_form_contents(
page: Page, page: Page,
form_selector: string, form_selector: string,
params: Record<string, boolean | string>, params: Record<string, boolean | string>,
): Promise<void> { ): Promise<void> {
for (const name of Object.keys(params)) { for (const name of Object.keys(params)) {
const name_selector = `${form_selector} [name="${name}"]`; const name_selector = `${form_selector} [name="${name}"]`;
const expected_value = params[name]; const expected_value = params[name];
@@ -189,53 +184,51 @@ class CommonUtils {
); );
} }
} }
} }
async get_element_text(element: ElementHandle<Element>): Promise<string> { export async function get_element_text(element: ElementHandle<Element>): Promise<string> {
const text = await (await element.getProperty("innerText"))!.jsonValue(); const text = await (await element.getProperty("innerText"))!.jsonValue();
assert.ok(typeof text === "string"); assert.ok(typeof text === "string");
return text; return text;
} }
async get_text_from_selector(page: Page, selector: string): Promise<string> { export async function get_text_from_selector(page: Page, selector: string): Promise<string> {
const elements = await page.$$(selector); const elements = await page.$$(selector);
const texts = await Promise.all( const texts = await Promise.all(elements.map(async (element) => get_element_text(element)));
elements.map(async (element) => this.get_element_text(element)),
);
return texts.join("").trim(); return texts.join("").trim();
} }
async get_stream_id(page: Page, stream_name: string): Promise<number> { export async function get_stream_id(page: Page, stream_name: string): Promise<number> {
return await page.evaluate( return await page.evaluate(
(stream_name: string) => zulip_test.get_stream_id(stream_name), (stream_name: string) => zulip_test.get_stream_id(stream_name),
stream_name, stream_name,
); );
} }
async get_user_id_from_name(page: Page, name: string): Promise<number> { export async function get_user_id_from_name(page: Page, name: string): Promise<number> {
if (this.fullname[name] !== undefined) { if (fullname[name] !== undefined) {
name = this.fullname[name]; name = fullname[name];
} }
return await page.evaluate((name: string) => zulip_test.get_user_id_from_name(name), name); return await page.evaluate((name: string) => zulip_test.get_user_id_from_name(name), name);
} }
async get_internal_email_from_name(page: Page, name: string): Promise<string> { export async function get_internal_email_from_name(page: Page, name: string): Promise<string> {
if (this.fullname[name] !== undefined) { if (fullname[name] !== undefined) {
name = this.fullname[name]; name = fullname[name];
} }
return await page.evaluate((fullname: string) => { return await page.evaluate((fullname: string) => {
const user_id = zulip_test.get_user_id_from_name(fullname); const user_id = zulip_test.get_user_id_from_name(fullname);
return zulip_test.get_person_by_user_id(user_id).email; return zulip_test.get_person_by_user_id(user_id).email;
}, name); }, name);
} }
async log_in( export async function log_in(
page: Page, page: Page,
credentials: {username: string; password: string} | null = null, credentials: {username: string; password: string} | null = null,
): Promise<void> { ): Promise<void> {
console.log("Logging in"); console.log("Logging in");
await page.goto(this.realm_url + "login/"); await page.goto(realm_url + "login/");
assert.equal(this.realm_url + "login/", page.url()); assert.equal(realm_url + "login/", page.url());
if (credentials === null) { if (credentials === null) {
credentials = test_credentials.default_user; credentials = test_credentials.default_user;
} }
@@ -244,14 +237,14 @@ class CommonUtils {
username: credentials.username, username: credentials.username,
password: credentials.password, password: credentials.password,
}; };
await this.fill_form(page, "#login_form", params); await fill_form(page, "#login_form", params);
await page.$eval("#login_form", (form) => (form as HTMLFormElement).submit()); await page.$eval("#login_form", (form) => (form as HTMLFormElement).submit());
await page.waitForSelector("#recent_topics_filter_buttons", {visible: true}); await page.waitForSelector("#recent_topics_filter_buttons", {visible: true});
} }
async log_out(page: Page): Promise<void> { export async function log_out(page: Page): Promise<void> {
await page.goto(this.realm_url); await page.goto(realm_url);
const menu_selector = "#settings-dropdown"; const menu_selector = "#settings-dropdown";
const logout_selector = '.dropdown-menu a[href="#logout"]'; const logout_selector = '.dropdown-menu a[href="#logout"]';
console.log("Logging out"); console.log("Logging out");
@@ -264,9 +257,13 @@ class CommonUtils {
// page is loaded. Then check that we are at the login url. // page is loaded. Then check that we are at the login url.
await page.waitForSelector('input[name="username"]'); await page.waitForSelector('input[name="username"]');
assert.ok(page.url().includes("/login/")); assert.ok(page.url().includes("/login/"));
} }
async ensure_enter_does_not_send(page: Page): Promise<void> { export function set_realm_url(new_realm_url: string): void {
realm_url = new_realm_url;
}
export async function ensure_enter_does_not_send(page: Page): Promise<void> {
let enter_sends = false; let enter_sends = false;
await page.$eval(".enter_sends_false", (el) => { await page.$eval(".enter_sends_false", (el) => {
if ((el as HTMLElement).style.display !== "none") { if ((el as HTMLElement).style.display !== "none") {
@@ -279,9 +276,12 @@ class CommonUtils {
await page.waitForSelector(enter_sends_false_selector); await page.waitForSelector(enter_sends_false_selector);
await page.click(enter_sends_false_selector); await page.click(enter_sends_false_selector);
} }
} }
async assert_compose_box_content(page: Page, expected_value: string): Promise<void> { export async function assert_compose_box_content(
page: Page,
expected_value: string,
): Promise<void> {
const compose_box_element = await page.waitForSelector("#compose-textarea"); const compose_box_element = await page.waitForSelector("#compose-textarea");
const compose_box_content = await page.evaluate((element) => { const compose_box_content = await page.evaluate((element) => {
if (!(element instanceof HTMLTextAreaElement)) { if (!(element instanceof HTMLTextAreaElement)) {
@@ -294,9 +294,9 @@ class CommonUtils {
expected_value, expected_value,
`Compose box content did not match with the expected value '{${expected_value}}'`, `Compose box content did not match with the expected value '{${expected_value}}'`,
); );
} }
async wait_for_fully_processed_message(page: Page, content: string): Promise<void> { export async function wait_for_fully_processed_message(page: Page, content: string): Promise<void> {
await page.waitForFunction( await page.waitForFunction(
(content: string) => { (content: string) => {
/* /*
@@ -350,17 +350,21 @@ class CommonUtils {
{}, {},
content, content,
); );
} }
// Wait for any previous send to finish, then send a message. // Wait for any previous send to finish, then send a message.
async send_message(page: Page, type: "stream" | "private", params: Message): Promise<void> { export async function send_message(
page: Page,
type: "stream" | "private",
params: Message,
): Promise<void> {
// If a message is outside the view, we do not need // If a message is outside the view, we do not need
// to wait for it to be processed later. // to wait for it to be processed later.
const outside_view = params.outside_view; const outside_view = params.outside_view;
delete params.outside_view; delete params.outside_view;
// Compose box content should be empty before sending the message. // Compose box content should be empty before sending the message.
await this.assert_compose_box_content(page, ""); await assert_compose_box_content(page, "");
if (type === "stream") { if (type === "stream") {
await page.keyboard.press("KeyC"); await page.keyboard.press("KeyC");
@@ -368,7 +372,7 @@ class CommonUtils {
await page.keyboard.press("KeyX"); await page.keyboard.press("KeyX");
const recipients = params.recipient!.split(", "); const recipients = params.recipient!.split(", ");
for (const recipient of recipients) { for (const recipient of recipients) {
await this.pm_recipient.set(page, recipient); await pm_recipient.set(page, recipient);
} }
delete params.recipient; delete params.recipient;
} else { } else {
@@ -385,32 +389,32 @@ class CommonUtils {
delete params.topic; delete params.topic;
} }
await this.fill_form(page, 'form[action^="/json/messages"]', params); await fill_form(page, 'form[action^="/json/messages"]', params);
await this.assert_compose_box_content(page, params.content); await assert_compose_box_content(page, params.content);
await this.ensure_enter_does_not_send(page); await ensure_enter_does_not_send(page);
await page.waitForSelector("#compose-send-button", {visible: true}); await page.waitForSelector("#compose-send-button", {visible: true});
await page.click("#compose-send-button"); await page.click("#compose-send-button");
// Sending should clear compose box content. // Sending should clear compose box content.
await this.assert_compose_box_content(page, ""); await assert_compose_box_content(page, "");
if (!outside_view) { if (!outside_view) {
await this.wait_for_fully_processed_message(page, params.content); await wait_for_fully_processed_message(page, params.content);
} }
// Close the compose box after sending the message. // Close the compose box after sending the message.
await page.evaluate(() => zulip_test.cancel_compose()); await page.evaluate(() => zulip_test.cancel_compose());
// Make sure the compose box is closed. // Make sure the compose box is closed.
await page.waitForSelector("#compose-textarea", {hidden: true}); await page.waitForSelector("#compose-textarea", {hidden: true});
} }
async send_multiple_messages(page: Page, msgs: Message[]): Promise<void> { export async function send_multiple_messages(page: Page, msgs: Message[]): Promise<void> {
for (const msg of msgs) { for (const msg of msgs) {
await this.send_message(page, msg.stream !== undefined ? "stream" : "private", msg); await send_message(page, msg.stream !== undefined ? "stream" : "private", msg);
}
} }
}
/** /**
* This method returns a array, which is formatted as: * This method returns a array, which is formatted as:
* [ * [
* ['stream > topic', ['message 1', 'message 2']], * ['stream > topic', ['message 1', 'message 2']],
@@ -419,15 +423,18 @@ class CommonUtils {
* *
* The messages are sorted chronologically. * The messages are sorted chronologically.
*/ */
async get_rendered_messages(page: Page, table = "zhome"): Promise<[string, string[]][]> { export async function get_rendered_messages(
page: Page,
table = "zhome",
): Promise<[string, string[]][]> {
const recipient_rows = await page.$$(`#${CSS.escape(table)} .recipient_row`); const recipient_rows = await page.$$(`#${CSS.escape(table)} .recipient_row`);
return Promise.all( return Promise.all(
recipient_rows.map(async (element): Promise<[string, string[]]> => { recipient_rows.map(async (element): Promise<[string, string[]]> => {
const stream_label = await element.$(".stream_label"); const stream_label = await element.$(".stream_label");
const stream_name = (await this.get_element_text(stream_label!)).trim(); const stream_name = (await get_element_text(stream_label!)).trim();
const topic_label = await element.$(".stream_topic a"); const topic_label = await element.$(".stream_topic a");
const topic_name = const topic_name =
topic_label === null ? "" : (await this.get_element_text(topic_label)).trim(); topic_label === null ? "" : (await get_element_text(topic_label)).trim();
let key = stream_name; let key = stream_name;
if (topic_name !== "") { if (topic_name !== "") {
// If topic_name is '' then this is PMs, so only // If topic_name is '' then this is PMs, so only
@@ -438,44 +445,44 @@ class CommonUtils {
const messages = await Promise.all( const messages = await Promise.all(
( (
await element.$$(".message_row .message_content") await element.$$(".message_row .message_content")
).map(async (message_row) => (await this.get_element_text(message_row)).trim()), ).map(async (message_row) => (await get_element_text(message_row)).trim()),
); );
return [key, messages]; return [key, messages];
}), }),
); );
} }
// This method takes in page, table to fetch the messages // This method takes in page, table to fetch the messages
// from, and expected messages. The format of expected // from, and expected messages. The format of expected
// message is { "stream > topic": [messages] }. // message is { "stream > topic": [messages] }.
// The method will only check that all the messages in the // The method will only check that all the messages in the
// messages array passed exist in the order they are passed. // messages array passed exist in the order they are passed.
async check_messages_sent( export async function check_messages_sent(
page: Page, page: Page,
table: string, table: string,
messages: [string, string[]][], messages: [string, string[]][],
): Promise<void> { ): Promise<void> {
await page.waitForSelector(`#${CSS.escape(table)}`, {visible: true}); await page.waitForSelector(`#${CSS.escape(table)}`, {visible: true});
const rendered_messages = await this.get_rendered_messages(page, table); const rendered_messages = await get_rendered_messages(page, table);
// We only check the last n messages because if we run // We only check the last n messages because if we run
// the test with --interactive there will be duplicates. // the test with --interactive there will be duplicates.
const last_n_messages = rendered_messages.slice(-messages.length); const last_n_messages = rendered_messages.slice(-messages.length);
assert.deepStrictEqual(last_n_messages, messages); assert.deepStrictEqual(last_n_messages, messages);
} }
async open_streams_modal(page: Page): Promise<void> { export async function open_streams_modal(page: Page): Promise<void> {
const all_streams_selector = "#subscribe-to-more-streams"; const all_streams_selector = "#subscribe-to-more-streams";
await page.waitForSelector(all_streams_selector, {visible: true}); await page.waitForSelector(all_streams_selector, {visible: true});
await page.click(all_streams_selector); await page.click(all_streams_selector);
await page.waitForSelector("#subscription_overlay.new-style", {visible: true}); await page.waitForSelector("#subscription_overlay.new-style", {visible: true});
const url = await this.page_url_with_fragment(page); const url = await page_url_with_fragment(page);
assert.ok(url.includes("#streams/all")); assert.ok(url.includes("#streams/all"));
} }
async manage_organization(page: Page): Promise<void> { export async function manage_organization(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown"; const menu_selector = "#settings-dropdown";
await page.waitForSelector(menu_selector, {visible: true}); await page.waitForSelector(menu_selector, {visible: true});
await page.click(menu_selector); await page.click(menu_selector);
@@ -484,21 +491,21 @@ class CommonUtils {
await page.click(organization_settings); await page.click(organization_settings);
await page.waitForSelector("#settings_overlay_container.show", {visible: true}); await page.waitForSelector("#settings_overlay_container.show", {visible: true});
const url = await this.page_url_with_fragment(page); const url = await page_url_with_fragment(page);
assert.match(url, /^http:\/\/[^/]+\/#organization/, "Unexpected manage organization URL"); assert.match(url, /^http:\/\/[^/]+\/#organization/, "Unexpected manage organization URL");
const organization_settings_data_section = "li[data-section='organization-settings']"; const organization_settings_data_section = "li[data-section='organization-settings']";
await page.click(organization_settings_data_section); await page.click(organization_settings_data_section);
} }
async select_item_via_typeahead( export async function select_item_via_typeahead(
page: Page, page: Page,
field_selector: string, field_selector: string,
str: string, str: string,
item: string, item: string,
): Promise<void> { ): Promise<void> {
console.log(`Looking in ${field_selector} to select ${str}, ${item}`); console.log(`Looking in ${field_selector} to select ${str}, ${item}`);
await this.clear_and_type(page, field_selector, str); await clear_and_type(page, field_selector, str);
const entry = await page.waitForSelector( const entry = await page.waitForSelector(
`xpath///*[@class="typeahead dropdown-menu" and contains(@style, "display: block")]//li[contains(normalize-space(), "${item}")]//a`, `xpath///*[@class="typeahead dropdown-menu" and contains(@style, "display: block")]//li[contains(normalize-space(), "${item}")]//a`,
{visible: true}, {visible: true},
@@ -511,30 +518,30 @@ class CommonUtils {
} }
entry.click(); entry.click();
}, entry); }, entry);
} }
async wait_for_modal_to_close(page: Page): Promise<void> { export async function wait_for_modal_to_close(page: Page): Promise<void> {
// This function will ensure that the mouse events are enabled for the background for further tests. // This function will ensure that the mouse events are enabled for the background for further tests.
await page.waitForFunction( await page.waitForFunction(
() => document.querySelector(".overlay.show")?.getAttribute("style") === null, () => document.querySelector(".overlay.show")?.getAttribute("style") === null,
); );
} }
async wait_for_micromodal_to_open(page: Page): Promise<void> { export async function wait_for_micromodal_to_open(page: Page): Promise<void> {
// We manually add the `modal--open` class to the modal after the modal animation completes. // We manually add the `modal--open` class to the modal after the modal animation completes.
await page.waitForFunction(() => document.querySelector(".modal--open") !== null); await page.waitForFunction(() => document.querySelector(".modal--open") !== null);
} }
async wait_for_micromodal_to_close(page: Page): Promise<void> { export async function wait_for_micromodal_to_close(page: Page): Promise<void> {
// This function will ensure that the mouse events are enabled for the background for further tests. // This function will ensure that the mouse events are enabled for the background for further tests.
await page.waitForFunction(() => document.querySelector(".modal--open") === null); await page.waitForFunction(() => document.querySelector(".modal--open") === null);
} }
async run_test_async(test_function: (page: Page) => Promise<void>): Promise<void> { export async function run_test_async(test_function: (page: Page) => Promise<void>): Promise<void> {
// Pass a page instance to test so we can take // Pass a page instance to test so we can take
// a screenshot of it when the test fails. // a screenshot of it when the test fails.
const browser = await this.ensure_browser(); const browser = await ensure_browser();
const page = await this.get_page(); const page = await get_page();
// Used to keep console messages in order after async source mapping // Used to keep console messages in order after async source mapping
let console_ready = Promise.resolve(); let console_ready = Promise.resolve();
@@ -551,7 +558,7 @@ class CommonUtils {
columnNumber: columnNumber === undefined ? undefined : columnNumber + 1, columnNumber: columnNumber === undefined ? undefined : columnNumber + 1,
}); });
try { try {
frame = await this.gps.getMappedLocation(frame); frame = await gps.getMappedLocation(frame);
} catch { } catch {
// Ignore source mapping errors // Ignore source mapping errors
} }
@@ -590,7 +597,7 @@ class CommonUtils {
ErrorStackParser.parse(error1).map(async (frame1) => { ErrorStackParser.parse(error1).map(async (frame1) => {
let frame = frame1 as unknown as StackFrame; let frame = frame1 as unknown as StackFrame;
try { try {
frame = await this.gps.getMappedLocation(frame); frame = await gps.getMappedLocation(frame);
} catch { } catch {
// Ignore source mapping errors // Ignore source mapping errors
} }
@@ -607,8 +614,8 @@ class CommonUtils {
console_ready = (async () => { console_ready = (async () => {
try { try {
// Take a screenshot, and increment the screenshot_id. // Take a screenshot, and increment the screenshot_id.
await this.screenshot(page, `failure-${this.screenshot_id}`); await screenshot(page, `failure-${screenshot_id}`);
this.screenshot_id += 1; screenshot_id += 1;
} finally { } finally {
await console_ready2; await console_ready2;
console.log("Closing page to stop the test..."); console.log("Closing page to stop the test...");
@@ -619,7 +626,7 @@ class CommonUtils {
try { try {
await test_function(page); await test_function(page);
await this.log_out(page); await log_out(page);
if (page_errored) { if (page_errored) {
throw new Error("Page threw an error"); throw new Error("Page threw an error");
@@ -627,8 +634,8 @@ class CommonUtils {
} catch (error: unknown) { } catch (error: unknown) {
if (!page_errored) { if (!page_errored) {
// Take a screenshot, and increment the screenshot_id. // Take a screenshot, and increment the screenshot_id.
await this.screenshot(page, `failure-${this.screenshot_id}`); await screenshot(page, `failure-${screenshot_id}`);
this.screenshot_id += 1; screenshot_id += 1;
} }
throw error; throw error;
@@ -636,14 +643,11 @@ class CommonUtils {
await console_ready; await console_ready;
await browser.close(); await browser.close();
} }
} }
run_test(test_function: (page: Page) => Promise<void>): void { export function run_test(test_function: (page: Page) => Promise<void>): void {
this.run_test_async(test_function).catch((error) => { run_test_async(test_function).catch((error) => {
console.error(error); console.error(error);
process.exit(1); process.exit(1);
}); });
}
} }
export default new CommonUtils();

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {ElementHandle, Page} from "puppeteer"; import type {ElementHandle, Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function submit_notifications_stream_settings(page: Page): Promise<void> { async function submit_notifications_stream_settings(page: Page): Promise<void> {
await page.waitForSelector('#org-submit-notifications[data-status="unsaved"]', {visible: true}); await page.waitForSelector('#org-submit-notifications[data-status="unsaved"]', {visible: true});

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {ElementHandle, Page} from "puppeteer"; import type {ElementHandle, Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function check_compose_form_empty(page: Page): Promise<void> { async function check_compose_form_empty(page: Page): Promise<void> {
await common.check_form_contents(page, "#send_message_form", { await common.check_form_contents(page, "#send_message_form", {

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function copy_messages( async function copy_messages(
page: Page, page: Page,

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
// This will be the row of the the custom profile field we add. // This will be the row of the the custom profile field we add.
const profile_field_row = "#admin_profile_fields_table tr:nth-last-child(1)"; const profile_field_row = "#admin_profile_fields_table tr:nth-last-child(1)";

View File

@@ -1,6 +1,6 @@
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function click_delete_and_return_last_msg_id(page: Page): Promise<string | undefined> { async function click_delete_and_return_last_msg_id(page: Page): Promise<string | undefined> {
return await page.evaluate(() => { return await page.evaluate(() => {

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function wait_for_drafts_to_disappear(page: Page): Promise<void> { async function wait_for_drafts_to_disappear(page: Page): Promise<void> {
await page.waitForFunction( await page.waitForFunction(

View File

@@ -1,6 +1,6 @@
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function trigger_edit_last_message(page: Page): Promise<void> { async function trigger_edit_last_message(page: Page): Promise<void> {
await page.evaluate(() => { await page.evaluate(() => {

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function test_mention(page: Page): Promise<void> { async function test_mention(page: Page): Promise<void> {
await common.log_in(page); await common.log_in(page);

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function get_stream_li(page: Page, stream_name: string): Promise<string> { async function get_stream_li(page: Page, stream_name: string): Promise<string> {
const stream_id = await common.get_stream_id(page, stream_name); const stream_id = await common.get_stream_id(page, stream_name);

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function wait_for_tab(page: Page, tab: string): Promise<void> { async function wait_for_tab(page: Page, tab: string): Promise<void> {
const tab_slector = `#${CSS.escape(tab)}.tab-pane`; const tab_slector = `#${CSS.escape(tab)}.tab-pane`;

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
const email = "alice@test.example.com"; const email = "alice@test.example.com";
const subdomain = "testsubdomain"; const subdomain = "testsubdomain";
@@ -67,7 +67,7 @@ async function realm_creation_tests(page: Page): Promise<void> {
await page.waitForSelector("#lightbox_overlay"); // if element doesn't exist,timeout error raises await page.waitForSelector("#lightbox_overlay"); // if element doesn't exist,timeout error raises
// Updating common.realm_url because we are redirecting to it when logging out. // Updating common.realm_url because we are redirecting to it when logging out.
common.realm_url = page.url(); common.set_realm_url(page.url());
} }
common.run_test(realm_creation_tests); common.run_test(realm_creation_tests);

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function test_add_linkifier(page: Page): Promise<void> { async function test_add_linkifier(page: Page): Promise<void> {
await page.waitForSelector(".admin-linkifier-form", {visible: true}); await page.waitForSelector(".admin-linkifier-form", {visible: true});

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
type Playground = { type Playground = {
playground_name: string; playground_name: string;

View File

@@ -3,7 +3,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import {test_credentials} from "../../var/puppeteer/test_credentials"; import {test_credentials} from "../../var/puppeteer/test_credentials";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
const OUTGOING_WEBHOOK_BOT_TYPE = "3"; const OUTGOING_WEBHOOK_BOT_TYPE = "3";
const GENERIC_BOT_TYPE = "1"; const GENERIC_BOT_TYPE = "1";

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
const message = "test star"; const message = "test star";

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function user_row_selector(page: Page, name: string): Promise<string> { async function user_row_selector(page: Page, name: string): Promise<string> {
const user_id = await common.get_user_id_from_name(page, name); const user_id = await common.get_user_id_from_name(page, name);

View File

@@ -1,6 +1,6 @@
import type {ElementHandle, Page} from "puppeteer"; import type {ElementHandle, Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function test_subscription_button(page: Page): Promise<void> { async function test_subscription_button(page: Page): Promise<void> {
const stream_selector = "[data-stream-name='Venice']"; const stream_selector = "[data-stream-name='Venice']";

View File

@@ -2,7 +2,7 @@ import {strict as assert} from "assert";
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function navigate_to_user_list(page: Page): Promise<void> { async function navigate_to_user_list(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown"; const menu_selector = "#settings-dropdown";

View File

@@ -1,6 +1,6 @@
import type {Page} from "puppeteer"; import type {Page} from "puppeteer";
import common from "../puppeteer_lib/common"; import * as common from "../puppeteer_lib/common";
async function open_set_user_status_modal(page: Page): Promise<void> { async function open_set_user_status_modal(page: Page): Promise<void> {
const menu_icon_selector = ".user_sidebar_entry:first-child .user-list-sidebar-menu-icon"; const menu_icon_selector = ".user_sidebar_entry:first-child .user-list-sidebar-menu-icon";