puppeteer_tests: Port to TypeScript.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2021-02-19 20:52:06 -08:00 committed by Tim Abbott
parent cdff4cfe53
commit 279c4b0e24
25 changed files with 461 additions and 369 deletions

View File

@ -6,5 +6,8 @@
/static/generated
/static/third
/static/webpack-bundles
/var
/var/*
!/var/puppeteer
/var/puppeteer/*
!/var/puppeteer/test_credentials.d.ts
/zulip-py3-venv

5
.gitignore vendored
View File

@ -27,7 +27,10 @@
package-lock.json
/.vagrant
/var
/var/*
!/var/puppeteer
/var/puppeteer/*
!/var/puppeteer/test_credentials.d.ts
/.dmypy.json

View File

@ -1,22 +1,22 @@
"use strict";
import {strict as assert} from "assert";
import "css.escape";
import path from "path";
const {strict: assert} = require("assert");
const path = require("path");
import {Browser, Page, launch} from "puppeteer";
require("css.escape");
const puppeteer = require("puppeteer");
const {test_credentials} = require("../../var/puppeteer/test_credentials");
import {test_credentials} from "../../var/puppeteer/test_credentials";
const root_dir = path.resolve(__dirname, "../../");
const puppeteer_dir = path.join(root_dir, "var/puppeteer");
type Message = Record<string, string | boolean> & {recipient?: string; content: string};
class CommonUtils {
browser = null;
browser: Browser | null = null;
screenshot_id = 0;
realm_url = "http://zulip.zulipdev.com:9981/";
pm_recipient = {
async set(page, recipient) {
async set(page: Page, recipient: string): Promise<void> {
// Without using the delay option here there seems to be
// a flake where the typeahead doesn't show up.
await page.type("#private_message_recipient", recipient, {delay: 100});
@ -37,7 +37,7 @@ class CommonUtils {
});
},
async expect(page, expected) {
async expect(page: Page, expected: string): Promise<void> {
const actual_recipients = await page.evaluate(() => {
const compose_state = window.require("./static/js/compose_state");
return compose_state.private_message_recipient();
@ -46,7 +46,7 @@ class CommonUtils {
},
};
fullname = {
fullname: Record<string, string> = {
cordelia: "Cordelia Lear",
othello: "Othello, the Moor of Venice",
hamlet: "King Hamlet",
@ -57,10 +57,10 @@ class CommonUtils {
height: 1024,
};
async ensure_browser() {
async ensure_browser(): Promise<Browser> {
if (this.browser === null) {
const {window_size} = this;
this.browser = await puppeteer.launch({
this.browser = await launch({
args: [
`--window-size=${window_size.width},${window_size.height}`,
"--no-sandbox",
@ -70,15 +70,16 @@ class CommonUtils {
headless: true,
});
}
return this.browser;
}
async get_page() {
await this.ensure_browser();
const page = await this.browser.newPage();
async get_page(): Promise<Page> {
const browser = await this.ensure_browser();
const page = await browser.newPage();
return page;
}
async screenshot(page, name = null) {
async screenshot(page: Page, name: string | null = null): Promise<void> {
if (name === null) {
name = `${this.screenshot_id}`;
this.screenshot_id += 1;
@ -107,8 +108,12 @@ class CommonUtils {
* terms: true
* });
*/
async fill_form(page, form_selector, params) {
async function is_dropdown(page, name) {
async fill_form(
page: Page,
form_selector: string,
params: Record<string, boolean | string>,
): Promise<void> {
async function is_dropdown(page: Page, name: string): Promise<boolean> {
return (await page.$(`select[name="${name}"]`)) !== null;
}
for (const name of Object.keys(params)) {
@ -118,37 +123,44 @@ class CommonUtils {
await page.$eval(
name_selector,
(el, value) => {
if (el.checked !== value) {
if (el instanceof HTMLInputElement && el.checked !== value) {
el.click();
}
},
value,
);
} else if (await is_dropdown(page, name)) {
await page.select(name_selector, params[name]);
if (typeof value !== "string") {
throw new TypeError(`Expected string for ${name}`);
}
await page.select(name_selector, value);
} else {
// clear any existing text in the input field before filling.
await page.$eval(name_selector, (el) => {
el.value = "";
(el as HTMLInputElement).value = "";
});
await page.type(name_selector, params[name]);
await page.type(name_selector, value);
}
}
}
async check_form_contents(page, form_selector, params) {
async check_form_contents(
page: Page,
form_selector: string,
params: Record<string, boolean | string>,
): Promise<void> {
for (const name of Object.keys(params)) {
const name_selector = `${form_selector} [name="${name}"]`;
const expected_value = params[name];
if (typeof expected_value === "boolean") {
assert.equal(
await page.$eval(name_selector, (el) => el.checked),
await page.$eval(name_selector, (el) => (el as HTMLInputElement).checked),
expected_value,
"Form content is not as expected.",
);
} else {
assert.equal(
await page.$eval(name_selector, (el) => el.value),
await page.$eval(name_selector, (el) => (el as HTMLInputElement).value),
expected_value,
"Form content is not as expected.",
);
@ -156,48 +168,51 @@ class CommonUtils {
}
}
async get_text_from_selector(page, selector) {
return await page.evaluate((selector) => $(selector).text().trim(), selector);
async get_text_from_selector(page: Page, selector: string): Promise<string> {
return await page.evaluate((selector: string) => $(selector).text().trim(), selector);
}
async wait_for_text(page, selector, text) {
async wait_for_text(page: Page, selector: string, text: string): Promise<void> {
await page.waitForFunction(
(selector, text) => $(selector).text().includes(text),
(selector: string, text: string) => $(selector).text().includes(text),
{},
selector,
text,
);
}
async get_stream_id(page, stream_name) {
return await page.evaluate((stream_name) => {
async get_stream_id(page: Page, stream_name: string): Promise<number> {
return await page.evaluate((stream_name: string) => {
const stream_data = window.require("./static/js/stream_data");
return stream_data.get_stream_id(stream_name);
}, stream_name);
}
async get_user_id_from_name(page, name) {
async get_user_id_from_name(page: Page, name: string): Promise<number> {
if (this.fullname[name] !== undefined) {
name = this.fullname[name];
}
return await page.evaluate((name) => {
const people = require("./static/js/people");
return await page.evaluate((name: string) => {
const people = window.require("./static/js/people");
return people.get_user_id_from_name(name);
}, name);
}
async get_internal_email_from_name(page, name) {
async get_internal_email_from_name(page: Page, name: string): Promise<string> {
if (this.fullname[name] !== undefined) {
name = this.fullname[name];
}
return await page.evaluate((fullname) => {
const people = require("./static/js/people");
return await page.evaluate((fullname: string) => {
const people = window.require("./static/js/people");
const user_id = people.get_user_id_from_name(fullname);
return people.get_by_user_id(user_id).email;
}, name);
}
async log_in(page, credentials = null) {
async log_in(
page: Page,
credentials: {username: string; password: string} | null = null,
): Promise<void> {
console.log("Logging in");
await page.goto(this.realm_url + "login/");
assert.equal(this.realm_url + "login/", page.url());
@ -210,12 +225,12 @@ class CommonUtils {
password: credentials.password,
};
await this.fill_form(page, "#login_form", params);
await page.$eval("#login_form", (form) => form.submit());
await page.$eval("#login_form", (form) => (form as HTMLFormElement).submit());
await page.waitForSelector("#zhome .message_row", {visible: true});
}
async log_out(page) {
async log_out(page: Page): Promise<void> {
await page.goto(this.realm_url);
const menu_selector = "#settings-dropdown";
const logout_selector = 'a[href="#logout"]';
@ -231,20 +246,20 @@ class CommonUtils {
assert(page.url().includes("/login/"));
}
async ensure_enter_does_not_send(page) {
async ensure_enter_does_not_send(page: Page): Promise<void> {
await page.$eval("#enter_sends", (el) => {
if (el.checked) {
el.click();
if ((el as HTMLInputElement).checked) {
(el as HTMLInputElement).click();
}
});
}
async assert_compose_box_content(page, expected_value) {
async assert_compose_box_content(page: Page, expected_value: string): Promise<void> {
await page.waitForSelector("#compose-textarea");
const compose_box_element = await page.$("#compose-textarea");
const compose_box_content = await page.evaluate(
(element) => element.value,
(element: HTMLTextAreaElement) => element.value,
compose_box_element,
);
assert.equal(
@ -254,9 +269,9 @@ class CommonUtils {
);
}
async wait_for_fully_processed_message(page, content) {
async wait_for_fully_processed_message(page: Page, content: string): Promise<void> {
await page.waitForFunction(
(content) => {
(content: string) => {
/*
The tricky part about making sure that
a message has actually been fully processed
@ -312,7 +327,7 @@ class CommonUtils {
}
// Wait for any previous send to finish, then send a message.
async send_message(page, type, params) {
async send_message(page: Page, type: "stream" | "private", params: Message): Promise<void> {
// If a message is outside the view, we do not need
// to wait for it to be processed later.
const outside_view = params.outside_view;
@ -325,7 +340,7 @@ class CommonUtils {
await page.keyboard.press("KeyC");
} else if (type === "private") {
await page.keyboard.press("KeyX");
const recipients = params.recipient.split(", ");
const recipients = params.recipient!.split(", ");
for (const recipient of recipients) {
await this.pm_recipient.set(page, recipient);
}
@ -366,7 +381,7 @@ class CommonUtils {
await page.waitForSelector("#compose-textarea", {hidden: true});
}
async send_multiple_messages(page, msgs) {
async send_multiple_messages(page: Page, msgs: Message[]): Promise<void> {
for (const msg of msgs) {
await this.send_message(page, msg.stream !== undefined ? "stream" : "private", msg);
}
@ -381,10 +396,10 @@ class CommonUtils {
*
* The messages are sorted chronologically.
*/
async get_rendered_messages(page, table = "zhome") {
return await page.evaluate((table) => {
async get_rendered_messages(page: Page, table = "zhome"): Promise<[string, string[]][]> {
return await page.evaluate((table: string) => {
const $recipient_rows = $(`#${CSS.escape(table)}`).find(".recipient_row");
return $recipient_rows.toArray().map((element) => {
return $recipient_rows.toArray().map((element): [string, string[]] => {
const $el = $(element);
const stream_name = $el.find(".stream_label").text().trim();
const topic_name = $el.find(".stream_topic a").text().trim();
@ -399,7 +414,7 @@ class CommonUtils {
const messages = $el
.find(".message_row .message_content")
.toArray()
.map((message_row) => message_row.textContent.trim());
.map((message_row) => message_row.textContent!.trim());
return [key, messages];
});
@ -411,7 +426,11 @@ class CommonUtils {
// message is { "stream > topic": [messages] }.
// The method will only check that all the messages in the
// messages array passed exist in the order they are passed.
async check_messages_sent(page, table, messages) {
async check_messages_sent(
page: Page,
table: string,
messages: [string, string[]][],
): Promise<void> {
await page.waitForSelector(`#${CSS.escape(table)}`, {visible: true});
const rendered_messages = await this.get_rendered_messages(page, table);
@ -421,7 +440,7 @@ class CommonUtils {
assert.deepStrictEqual(last_n_messages, messages);
}
async manage_organization(page) {
async manage_organization(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown";
await page.waitForSelector(menu_selector, {visible: true});
await page.click(menu_selector);
@ -437,10 +456,15 @@ class CommonUtils {
await page.click(organization_settings_data_section);
}
async select_item_via_typeahead(page, field_selector, str, item) {
async select_item_via_typeahead(
page: Page,
field_selector: string,
str: string,
item: string,
): Promise<void> {
console.log(`Looking in ${field_selector} to select ${str}, ${item}`);
await page.evaluate(
(field_selector, str, item) => {
(field_selector: string, str: string, item: string) => {
// Set the value and then send a bogus keyup event to trigger
// the typeahead.
$(field_selector)
@ -464,26 +488,26 @@ class CommonUtils {
);
}
async run_test(test_function) {
async run_test(test_function: (page: Page) => Promise<void>): Promise<void> {
// Pass a page instance to test so we can take
// a screenshot of it when the test fails.
const browser = await this.ensure_browser();
const page = await this.get_page();
try {
await test_function(page);
} catch (error) {
} catch (error: unknown) {
console.log(error);
// Take a screenshot, and increment the screenshot_id.
await this.screenshot(page, `failure-${this.screenshot_id}`);
this.screenshot_id += 1;
await this.browser.close();
await browser.close();
process.exit(1);
} finally {
this.browser.close();
await browser.close();
}
}
}
const common = new CommonUtils();
module.exports = common;
export default new CommonUtils();

View File

@ -1,15 +1,15 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
const email = "alice@test.example.com";
const subdomain = "testsubdomain";
const organization_name = "Awesome Organization";
const host = "zulipdev.com:9981";
async function realm_creation_tests(page) {
async function realm_creation_tests(page: Page): Promise<void> {
await page.goto("http://" + host + "/new/");
// submit the email for realm creation.
@ -17,7 +17,7 @@ async function realm_creation_tests(page) {
await page.type("#email", email);
await Promise.all([
page.waitForNavigation(),
page.$eval("#send_confirm", (form) => form.submit()),
page.$eval("#send_confirm", (form) => (form as HTMLFormElement).submit()),
]);
// Make sure onfirmation email is sent.
@ -27,8 +27,8 @@ async function realm_creation_tests(page) {
await page.goto("http://" + host + "/confirmation_key/");
// Open the confirmation URL
const page_content = await page.evaluate(() => document.querySelector("body").textContent);
const confirmation_key = await JSON.parse(page_content).confirmation_key;
const page_content = await page.evaluate(() => document.querySelector("body")!.textContent);
const confirmation_key = await JSON.parse(page_content!).confirmation_key;
const confirmation_url = "http://" + host + "/accounts/do_confirm/" + confirmation_key;
await page.goto(confirmation_url);
@ -43,7 +43,9 @@ async function realm_creation_tests(page) {
// Make sure the realm creation page is loaded correctly by
// checking the text in <p> tag under pitch class is as expected.
await page.waitForSelector(".pitch");
const text_in_pitch = await page.evaluate(() => document.querySelector(".pitch p").textContent);
const text_in_pitch = await page.evaluate(
() => document.querySelector(".pitch p")!.textContent,
);
assert(text_in_pitch === "Youre almost there! We just need you to do one last thing.");
// fill the form.
@ -56,9 +58,9 @@ async function realm_creation_tests(page) {
};
// For some reason, page.click() does not work this for particular checkbox
// so use page.$eval here to call the .click method in the browser.
await page.$eval("#realm_in_root_domain", (el) => el.click());
await page.$eval("#realm_in_root_domain", (el) => (el as HTMLInputElement).click());
await common.fill_form(page, "#registration", params);
await page.$eval("#registration", (form) => form.submit());
await page.$eval("#registration", (form) => (form as HTMLFormElement).submit());
// Check if realm is created and user is logged in by checking if
// element of id `lightbox_overlay` exists.

View File

@ -1,11 +0,0 @@
"use strict";
const {test_credentials} = require("../../var/puppeteer/test_credentials");
const common = require("../puppeteer_lib/common");
async function login_tests(page) {
await common.log_in(page, test_credentials.default_user);
await common.log_out(page);
}
common.run_test(login_tests);

View File

@ -0,0 +1,11 @@
import type {Page} from "puppeteer";
import {test_credentials} from "../../var/puppeteer/test_credentials";
import common from "../puppeteer_lib/common";
async function login_tests(page: Page): Promise<void> {
await common.log_in(page, test_credentials.default_user);
await common.log_out(page);
}
common.run_test(login_tests);

View File

@ -1,15 +1,15 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function get_stream_li(page, stream_name) {
async function get_stream_li(page: Page, stream_name: string): Promise<string> {
const stream_id = await common.get_stream_id(page, stream_name);
return `#stream_filters [data-stream-id="${CSS.escape(stream_id)}"]`;
return `#stream_filters [data-stream-id="${CSS.escape(stream_id.toString())}"]`;
}
async function expect_home(page) {
async function expect_home(page: Page): Promise<void> {
await common.check_messages_sent(page, "zhome", [
["Verona > test", ["verona test a", "verona test b"]],
["Verona > other topic", ["verona other topic c"]],
@ -22,7 +22,7 @@ async function expect_home(page) {
]);
}
async function expect_verona_stream(page) {
async function expect_verona_stream(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["Verona > test", ["verona test a", "verona test b"]],
@ -32,7 +32,7 @@ async function expect_verona_stream(page) {
assert.strictEqual(await page.title(), "Verona - Zulip Dev - Zulip");
}
async function expect_verona_stream_test_topic(page) {
async function expect_verona_stream_test_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["Verona > test", ["verona test a", "verona test b", "verona test d"]],
@ -43,14 +43,14 @@ async function expect_verona_stream_test_topic(page) {
);
}
async function expect_verona_other_topic(page) {
async function expect_verona_other_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["Verona > other topic", ["verona other topic c"]],
]);
}
async function expect_test_topic(page) {
async function expect_test_topic(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["Verona > test", ["verona test a", "verona test b"]],
@ -59,7 +59,7 @@ async function expect_test_topic(page) {
]);
}
async function expect_huddle(page) {
async function expect_huddle(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["You and Cordelia Lear, King Hamlet", ["group pm a", "group pm b", "group pm d"]],
@ -67,12 +67,12 @@ async function expect_huddle(page) {
assert.strictEqual(await page.title(), "Cordelia Lear, King Hamlet - Zulip Dev - Zulip");
}
async function expect_cordelia_private_narrow(page) {
async function expect_cordelia_private_narrow(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [["You and Cordelia Lear", ["pm c", "pm e"]]]);
}
async function un_narrow(page) {
async function un_narrow(page: Page): Promise<void> {
if (await page.evaluate(() => $(".message_comp").is(":visible"))) {
await page.keyboard.press("Escape");
}
@ -81,11 +81,11 @@ async function un_narrow(page) {
assert.strictEqual(await page.title(), "home - Zulip Dev - Zulip");
}
async function un_narrow_by_clicking_org_icon(page) {
async function un_narrow_by_clicking_org_icon(page: Page): Promise<void> {
await page.click(".brand");
}
async function test_navigations_from_home(page) {
async function test_navigations_from_home(page: Page): Promise<void> {
console.log("Narrowing by clicking stream");
await page.evaluate(() => $(`*[title='Narrow to stream "Verona"']`).trigger("click"));
await expect_verona_stream(page);
@ -113,7 +113,13 @@ async function test_navigations_from_home(page) {
await expect_home(page);
}
async function search_and_check(page, search_str, item_to_select, check, expected_narrow_title) {
async function search_and_check(
page: Page,
search_str: string,
item_to_select: string,
check: (page: Page) => Promise<void>,
expected_narrow_title: string,
): Promise<void> {
await common.select_item_via_typeahead(page, "#search_query", search_str, item_to_select);
await check(page);
assert.strictEqual(await page.title(), expected_narrow_title);
@ -121,7 +127,7 @@ async function search_and_check(page, search_str, item_to_select, check, expecte
await expect_home(page);
}
async function search_silent_user(page, str, item) {
async function search_silent_user(page: Page, str: string, item: string): Promise<void> {
await common.select_item_via_typeahead(page, "#search_query", str, item);
await page.waitForSelector("#silent_user", {visible: true});
const expect_message = "You haven't received any messages sent by this user yet!";
@ -129,7 +135,7 @@ async function search_silent_user(page, str, item) {
await un_narrow(page);
}
async function expect_non_existing_user(page) {
async function expect_non_existing_user(page: Page): Promise<void> {
await page.waitForSelector("#non_existing_user", {visible: true});
const expected_message = "This user does not exist!";
assert.strictEqual(
@ -138,7 +144,7 @@ async function expect_non_existing_user(page) {
);
}
async function expect_non_existing_users(page) {
async function expect_non_existing_users(page: Page): Promise<void> {
await page.waitForSelector("#non_existing_users", {visible: true});
const expected_message = "One or more of these users do not exist!";
assert.strictEqual(
@ -147,13 +153,13 @@ async function expect_non_existing_users(page) {
);
}
async function search_non_existing_user(page, str, item) {
async function search_non_existing_user(page: Page, str: string, item: string): Promise<void> {
await common.select_item_via_typeahead(page, "#search_query", str, item);
await expect_non_existing_user(page);
await un_narrow(page);
}
async function search_tests(page) {
async function search_tests(page: Page): Promise<void> {
await search_and_check(
page,
"Verona",
@ -217,7 +223,7 @@ async function search_tests(page) {
);
}
async function expect_all_pm(page) {
async function expect_all_pm(page: Page): Promise<void> {
await page.waitForSelector("#zfilt", {visible: true});
await common.check_messages_sent(page, "zfilt", [
["You and Cordelia Lear, King Hamlet", ["group pm a", "group pm b"]],
@ -232,7 +238,7 @@ async function expect_all_pm(page) {
assert.strictEqual(await page.title(), "Private messages - Zulip Dev - Zulip");
}
async function test_narrow_by_clicking_the_left_sidebar(page) {
async function test_narrow_by_clicking_the_left_sidebar(page: Page): Promise<void> {
console.log("Narrowing with left sidebar");
await page.click((await get_stream_li(page, "Verona")) + " a");
@ -247,11 +253,11 @@ async function test_narrow_by_clicking_the_left_sidebar(page) {
await un_narrow(page);
}
async function arrow(page, direction) {
await page.keyboard.press(`Arrow${direction}`);
async function arrow(page: Page, direction: "Up" | "Down"): Promise<void> {
await page.keyboard.press(({Up: "ArrowUp", Down: "ArrowDown"} as const)[direction]);
}
async function test_search_venice(page) {
async function test_search_venice(page: Page): Promise<void> {
await page.evaluate(() => {
$(".stream-list-filter")
.expectOne()
@ -279,7 +285,7 @@ async function test_search_venice(page) {
await page.waitForSelector(".input-append.notdisplayed");
}
async function test_stream_search_filters_stream_list(page) {
async function test_stream_search_filters_stream_list(page: Page): Promise<void> {
console.log("Search streams using left side bar");
await page.waitForSelector(".input-append.notdisplayed"); // Stream filter box invisible initially
@ -342,22 +348,22 @@ async function test_stream_search_filters_stream_list(page) {
await un_narrow(page);
}
async function test_users_search(page) {
async function test_users_search(page: Page): Promise<void> {
console.log("Search users using right sidebar");
async function assert_in_list(page, name) {
async function assert_in_list(page: Page, name: string): Promise<void> {
await page.waitForSelector(`#user_presences li [data-name="${CSS.escape(name)}"]`, {
visible: true,
});
}
async function assert_selected(page, name) {
async function assert_selected(page: Page, name: string): Promise<void> {
await page.waitForSelector(
`#user_presences li.highlighted_user [data-name="${CSS.escape(name)}"]`,
{visible: true},
);
}
async function assert_not_selected(page, name) {
async function assert_not_selected(page: Page, name: string): Promise<void> {
await page.waitForSelector(
`#user_presences li.highlighted_user [data-name="${CSS.escape(name)}"]`,
{hidden: true},
@ -406,7 +412,7 @@ async function test_users_search(page) {
await expect_cordelia_private_narrow(page);
}
async function message_basic_tests(page) {
async function message_basic_tests(page: Page): Promise<void> {
await common.log_in(page);
console.log("Sending messages");

View File

@ -1,10 +1,10 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function check_compose_form_empty(page) {
async function check_compose_form_empty(page: Page): Promise<void> {
await common.check_form_contents(page, "#send_message_form", {
stream_message_recipient_stream: "",
stream_message_recipient_topic: "",
@ -12,20 +12,20 @@ async function check_compose_form_empty(page) {
});
}
async function close_compose_box(page) {
async function close_compose_box(page: Page): Promise<void> {
await page.keyboard.press("Escape");
await page.waitForSelector("#compose-textarea", {hidden: true});
}
function get_message_xpath(text) {
function get_message_xpath(text: string): string {
return `//p[text()='${text}']`;
}
function get_last_element(array) {
function get_last_element<T>(array: T[]): T {
return array.slice(-1)[0];
}
async function test_send_messages(page) {
async function test_send_messages(page: Page): Promise<void> {
const initial_msgs_count = await page.evaluate(() => $("#zhome .message_row").length);
await common.send_multiple_messages(page, [
@ -39,26 +39,26 @@ async function test_send_messages(page) {
);
}
async function test_stream_compose_keyboard_shortcut(page) {
async function test_stream_compose_keyboard_shortcut(page: Page): Promise<void> {
await page.keyboard.press("KeyC");
await page.waitForSelector("#stream-message", {visible: true});
await check_compose_form_empty(page);
await close_compose_box(page);
}
async function test_private_message_compose_shortcut(page) {
async function test_private_message_compose_shortcut(page: Page): Promise<void> {
await page.keyboard.press("KeyX");
await page.waitForSelector("#private_message_recipient", {visible: true});
await common.pm_recipient.expect(page, "");
await close_compose_box(page);
}
async function test_keyboard_shortcuts(page) {
async function test_keyboard_shortcuts(page: Page): Promise<void> {
await test_stream_compose_keyboard_shortcut(page);
await test_private_message_compose_shortcut(page);
}
async function test_reply_by_click_prepopulates_stream_topic_names(page) {
async function test_reply_by_click_prepopulates_stream_topic_names(page: Page): Promise<void> {
const stream_message = get_last_element(
await page.$x(get_message_xpath("Compose stream reply test")),
);
@ -72,7 +72,9 @@ async function test_reply_by_click_prepopulates_stream_topic_names(page) {
await close_compose_box(page);
}
async function test_reply_by_click_prepopulates_private_message_recipient(page) {
async function test_reply_by_click_prepopulates_private_message_recipient(
page: Page,
): Promise<void> {
const private_message = get_last_element(
await page.$x(get_message_xpath("Compose private message reply test")),
);
@ -85,7 +87,7 @@ async function test_reply_by_click_prepopulates_private_message_recipient(page)
await close_compose_box(page);
}
async function test_reply_with_r_shortcut(page) {
async function test_reply_with_r_shortcut(page: Page): Promise<void> {
// The last message(private) in the narrow is currently selected as a result of previous tests.
// Now we go up and open compose box with r key.
await page.keyboard.press("KeyK");
@ -97,7 +99,7 @@ async function test_reply_with_r_shortcut(page) {
});
}
async function test_open_close_compose_box(page) {
async function test_open_close_compose_box(page: Page): Promise<void> {
await page.waitForSelector("#stream-message", {visible: true});
await close_compose_box(page);
await page.waitForSelector("#stream-message", {hidden: true});
@ -108,12 +110,12 @@ async function test_open_close_compose_box(page) {
await page.waitForSelector("#private-message", {hidden: true});
}
async function test_narrow_to_private_messages_with_cordelia(page) {
async function test_narrow_to_private_messages_with_cordelia(page: Page): Promise<void> {
const you_and_cordelia_selector =
'*[title="Narrow to your private messages with Cordelia Lear"]';
// For some unknown reason page.click() isn't working here.
await page.evaluate(
(selector) => document.querySelector(selector).click(),
(selector: string) => document.querySelector<HTMLElement>(selector)!.click(),
you_and_cordelia_selector,
);
const cordelia_user_id = await common.get_user_id_from_name(page, "Cordelia Lear");
@ -129,7 +131,7 @@ async function test_narrow_to_private_messages_with_cordelia(page) {
await close_compose_box(page);
}
async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page) {
async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page: Page): Promise<void> {
const recipients = ["cordelia@zulip.com", "othello@zulip.com"];
const multiple_recipients_pm = "A huddle to check spaces";
const pm_selector = `.messagebox:contains('${CSS.escape(multiple_recipients_pm)}')`;
@ -143,8 +145,8 @@ async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page) {
await page.keyboard.press("Escape");
await page.waitForSelector("#zhome .message_row", {visible: true});
await page.waitForFunction((selector) => $(selector).length !== 0, {}, pm_selector);
await page.evaluate((selector) => {
await page.waitForFunction((selector: string) => $(selector).length !== 0, {}, pm_selector);
await page.evaluate((selector: string) => {
$(selector).slice(-1)[0].click();
}, pm_selector);
await page.waitForSelector("#compose-textarea", {visible: true});
@ -158,7 +160,7 @@ async function test_send_multirecipient_pm_from_cordelia_pm_narrow(page) {
const markdown_preview_button = "#markdown_preview";
const markdown_preview_hide_button = "#undo_markdown_preview";
async function test_markdown_preview_buttons_visibility(page) {
async function test_markdown_preview_buttons_visibility(page: Page): Promise<void> {
await page.waitForSelector(markdown_preview_button, {visible: true});
await page.waitForSelector(markdown_preview_hide_button, {hidden: true});
@ -173,22 +175,22 @@ async function test_markdown_preview_buttons_visibility(page) {
await page.waitForSelector(markdown_preview_hide_button, {hidden: true});
}
async function test_markdown_preview_without_any_content(page) {
async function test_markdown_preview_without_any_content(page: Page): Promise<void> {
await page.click("#markdown_preview");
await page.waitForSelector("#undo_markdown_preview", {visible: true});
const markdown_preview_element = await page.$("#preview_content");
assert.equal(
await page.evaluate((element) => element.textContent, markdown_preview_element),
await page.evaluate((element: Element) => element.textContent, markdown_preview_element),
"Nothing to preview",
);
await page.click("#undo_markdown_preview");
}
async function test_markdown_rendering(page) {
async function test_markdown_rendering(page: Page): Promise<void> {
await page.waitForSelector("#markdown_preview", {visible: true});
let markdown_preview_element = await page.$("#preview_content");
assert.equal(
await page.evaluate((element) => element.textContent, markdown_preview_element),
await page.evaluate((element: Element) => element.textContent, markdown_preview_element),
"",
);
await common.fill_form(page, 'form[action^="/json/messages"]', {
@ -201,18 +203,18 @@ async function test_markdown_rendering(page) {
await page.waitForFunction(() => $("#preview_content").html() !== "");
markdown_preview_element = await page.$("#preview_content");
assert.equal(
await page.evaluate((element) => element.innerHTML, markdown_preview_element),
await page.evaluate((element: Element) => element.innerHTML, markdown_preview_element),
expected_markdown_html,
);
}
async function test_markdown_preview(page) {
async function test_markdown_preview(page: Page): Promise<void> {
await test_markdown_preview_buttons_visibility(page);
await test_markdown_preview_without_any_content(page);
await test_markdown_rendering(page);
}
async function compose_tests(page) {
async function compose_tests(page: Page): Promise<void> {
await common.log_in(page);
await test_send_messages(page);
await test_keyboard_shortcuts(page);

View File

@ -1,43 +1,44 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {ElementHandle, Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function user_checkbox(page, name) {
async function user_checkbox(page: Page, name: string): Promise<string> {
const user_id = await common.get_user_id_from_name(page, name);
return `#user-checkboxes [data-user-id="${CSS.escape(user_id)}"]`;
return `#user-checkboxes [data-user-id="${CSS.escape(user_id.toString())}"]`;
}
async function user_span(page, name) {
async function user_span(page: Page, name: string): Promise<string> {
return (await user_checkbox(page, name)) + " input ~ span";
}
async function stream_checkbox(page, stream_name) {
async function stream_checkbox(page: Page, stream_name: string): Promise<string> {
const stream_id = await common.get_stream_id(page, stream_name);
return `#stream-checkboxes [data-stream-id="${CSS.escape(stream_id)}"]`;
return `#stream-checkboxes [data-stream-id="${CSS.escape(stream_id.toString())}"]`;
}
async function stream_span(page, stream_name) {
async function stream_span(page: Page, stream_name: string): Promise<string> {
return (await stream_checkbox(page, stream_name)) + " input ~ span";
}
async function wait_for_checked(page, user_name, is_checked) {
async function wait_for_checked(page: Page, user_name: string, is_checked: boolean): Promise<void> {
const selector = await user_checkbox(page, user_name);
await page.waitForFunction(
(selector, is_checked) => $(selector).find("input")[0].checked === is_checked,
(selector: string, is_checked: boolean) =>
$(selector).find("input")[0].checked === is_checked,
{},
selector,
is_checked,
);
}
async function stream_name_error(page) {
async function stream_name_error(page: Page): Promise<string> {
await page.waitForSelector("#stream_name_error", {visible: true});
return await common.get_text_from_selector(page, "#stream_name_error");
}
async function open_streams_modal(page) {
async function open_streams_modal(page: Page): Promise<void> {
const all_streams_selector = 'a[href="#streams/all"]';
await page.waitForSelector(all_streams_selector, {visible: true});
await page.click(all_streams_selector);
@ -46,16 +47,16 @@ async function open_streams_modal(page) {
assert(page.url().includes("#streams/all"));
}
async function test_subscription_button_verona_stream(page) {
async function test_subscription_button_verona_stream(page: Page): Promise<void> {
const button_selector = "[data-stream-name='Verona'] .sub_unsub_button";
const subscribed_selector = `${button_selector}.checked`;
const unsubscribed_selector = `${button_selector}:not(.checked)`;
async function subscribed() {
async function subscribed(): Promise<ElementHandle | null> {
return await page.waitForSelector(subscribed_selector, {visible: true});
}
async function unsubscribed() {
async function unsubscribed(): Promise<ElementHandle | null> {
return await page.waitForSelector(unsubscribed_selector, {visible: true});
}
@ -66,57 +67,69 @@ async function test_subscription_button_verona_stream(page) {
// We assume Verona is already subscribed, so the first line here
// should happen immediately.
button = await subscribed();
button.click();
button!.click();
button = await unsubscribed();
button.click();
button!.click();
button = await subscribed();
button.click();
button!.click();
button = await unsubscribed();
button.click();
button!.click();
button = await subscribed();
}
async function click_create_new_stream(page, cordelia_checkbox, othello_checkbox) {
async function click_create_new_stream(
page: Page,
cordelia_checkbox: string,
othello_checkbox: string,
): Promise<void> {
await page.click("#add_new_subscription .create_stream_button");
await page.waitForSelector(cordelia_checkbox, {visible: true});
await page.waitForSelector(othello_checkbox, {visible: true});
}
async function open_copy_from_stream_dropdown(page, scotland_checkbox, rome_checkbox) {
async function open_copy_from_stream_dropdown(
page: Page,
scotland_checkbox: string,
rome_checkbox: string,
): Promise<void> {
await page.click("#copy-from-stream-expand-collapse .control-label");
await page.waitForSelector(scotland_checkbox, {visible: true});
await page.waitForSelector(rome_checkbox, {visible: true});
}
async function test_check_all_only_affects_visible_users(page) {
async function test_check_all_only_affects_visible_users(page: Page): Promise<void> {
await page.click(".subs_set_all_users");
await wait_for_checked(page, "cordelia", false);
await wait_for_checked(page, "othello", true);
}
async function test_uncheck_all(page) {
async function test_uncheck_all(page: Page): Promise<void> {
await page.click(".subs_unset_all_users");
await wait_for_checked(page, "othello", false);
}
async function clear_ot_filter_with_backspace(page) {
async function clear_ot_filter_with_backspace(page: Page): Promise<void> {
await page.click(".add-user-list-filter");
await page.keyboard.press("Backspace");
await page.keyboard.press("Backspace");
}
async function verify_filtered_users_are_visible_again(page, cordelia_checkbox, othello_checkbox) {
async function verify_filtered_users_are_visible_again(
page: Page,
cordelia_checkbox: string,
othello_checkbox: string,
): Promise<void> {
await page.waitForSelector(cordelia_checkbox, {visible: true});
await page.waitForSelector(othello_checkbox, {visible: true});
}
async function test_user_filter_ui(
page,
cordelia_checkbox,
othello_checkbox,
scotland_checkbox,
rome_checkbox,
) {
page: Page,
cordelia_checkbox: string,
othello_checkbox: string,
scotland_checkbox: string,
rome_checkbox: string,
): Promise<void> {
await page.waitForSelector("form#stream_creation_form", {visible: true});
await common.fill_form(page, "form#stream_creation_form", {user_list_filter: "ot"});
@ -135,7 +148,7 @@ async function test_user_filter_ui(
await verify_filtered_users_are_visible_again(page, cordelia_checkbox, othello_checkbox);
}
async function create_stream(page) {
async function create_stream(page: Page): Promise<void> {
await page.waitForXPath('//*[text()="Create stream"]', {visible: true});
await common.fill_form(page, "form#stream_creation_form", {
stream_name: "Puppeteer",
@ -162,13 +175,13 @@ async function create_stream(page) {
// Assert subscriber count becomes 5(scotland(+4), cordelia(+1), othello(-1), Desdemona(+1)).
await page.waitForFunction(
(subscriber_count_selector) => $(subscriber_count_selector).text().trim() === "5",
(subscriber_count_selector: string) => $(subscriber_count_selector).text().trim() === "5",
{},
subscriber_count_selector,
);
}
async function test_streams_with_empty_names_cannot_be_created(page) {
async function test_streams_with_empty_names_cannot_be_created(page: Page): Promise<void> {
await page.click("#add_new_subscription .create_stream_button");
await page.waitForSelector("form#stream_creation_form", {visible: true});
await common.fill_form(page, "form#stream_creation_form", {stream_name: " "});
@ -176,7 +189,7 @@ async function test_streams_with_empty_names_cannot_be_created(page) {
assert.strictEqual(await stream_name_error(page), "A stream needs to have a name");
}
async function test_streams_with_duplicate_names_cannot_be_created(page) {
async function test_streams_with_duplicate_names_cannot_be_created(page: Page): Promise<void> {
await common.fill_form(page, "form#stream_creation_form", {stream_name: "Puppeteer"});
await page.click("form#stream_creation_form button.button.sea-green");
assert.strictEqual(await stream_name_error(page), "A stream with this name already exists");
@ -185,7 +198,7 @@ async function test_streams_with_duplicate_names_cannot_be_created(page) {
await page.click(cancel_button_selector);
}
async function test_stream_creation(page) {
async function test_stream_creation(page: Page): Promise<void> {
const cordelia_checkbox = await user_checkbox(page, "cordelia");
const othello_checkbox = await user_checkbox(page, "othello");
const scotland_checkbox = await stream_checkbox(page, "Scotland");
@ -205,7 +218,7 @@ async function test_stream_creation(page) {
await test_streams_with_duplicate_names_cannot_be_created(page);
}
async function test_streams_search_feature(page) {
async function test_streams_search_feature(page: Page): Promise<void> {
assert.strictEqual(await common.get_text_from_selector(page, "#search_stream_name"), "");
const hidden_streams_selector = ".stream-row.notdisplayed .stream-name";
assert.strictEqual(
@ -235,7 +248,7 @@ async function test_streams_search_feature(page) {
);
}
async function subscriptions_tests(page) {
async function subscriptions_tests(page: Page): Promise<void> {
await common.log_in(page);
await open_streams_modal(page);
await test_subscription_button_verona_stream(page);

View File

@ -1,17 +1,17 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
const message = "test star";
async function stars_count(page) {
async function stars_count(page: Page): Promise<number> {
return await page.evaluate(() => $("#zhome .fa-star:not(.empty-star)").length);
}
async function toggle_test_star_message(page) {
await page.evaluate((message) => {
async function toggle_test_star_message(page: Page): Promise<void> {
await page.evaluate((message: string) => {
const msg = $(`.message_content:contains("${CSS.escape(message)}"):visible`).last();
if (msg.length !== 1) {
throw new Error("cannot find test star message");
@ -26,7 +26,7 @@ async function toggle_test_star_message(page) {
}, message);
}
async function test_narrow_to_starred_messages(page) {
async function test_narrow_to_starred_messages(page: Page): Promise<void> {
await page.click('a[href^="#narrow/is/starred"]');
await common.check_messages_sent(page, "zfilt", [["Verona > stars", [message]]]);
@ -35,7 +35,7 @@ async function test_narrow_to_starred_messages(page) {
await page.waitForSelector("#zhome .message_row", {visible: true});
}
async function stars_test(page) {
async function stars_test(page: Page): Promise<void> {
await common.log_in(page);
await common.send_message(page, "stream", {
stream: "Verona",

View File

@ -1,8 +1,8 @@
"use strict";
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function trigger_edit_last_message(page) {
async function trigger_edit_last_message(page: Page): Promise<void> {
await page.evaluate(() => {
const msg = $("#zhome .message_row").last();
msg.find(".info").trigger("click");
@ -11,7 +11,7 @@ async function trigger_edit_last_message(page) {
await page.waitForSelector(".message_edit_content", {visible: true});
}
async function edit_stream_message(page, topic, content) {
async function edit_stream_message(page: Page, topic: string, content: string): Promise<void> {
await trigger_edit_last_message(page);
await page.evaluate(() => $(".message_edit_topic").val(""));
@ -24,7 +24,7 @@ async function edit_stream_message(page, topic, content) {
await common.wait_for_fully_processed_message(page, content);
}
async function test_stream_message_edit(page) {
async function test_stream_message_edit(page: Page): Promise<void> {
await common.send_message(page, "stream", {
stream: "Verona",
topic: "edits",
@ -36,7 +36,7 @@ async function test_stream_message_edit(page) {
await common.check_messages_sent(page, "zhome", [["Verona > edited", ["test edited"]]]);
}
async function test_edit_message_with_slash_me(page) {
async function test_edit_message_with_slash_me(page: Page): Promise<void> {
await common.send_message(page, "stream", {
stream: "Verona",
topic: "edits",
@ -59,7 +59,7 @@ async function test_edit_message_with_slash_me(page) {
);
}
async function test_edit_private_message(page) {
async function test_edit_private_message(page: Page): Promise<void> {
await common.send_message(page, "private", {
recipient: "cordelia@zulip.com",
content: "test editing pm",
@ -76,7 +76,7 @@ async function test_edit_private_message(page) {
]);
}
async function edit_tests(page) {
async function edit_tests(page: Page): Promise<void> {
await common.log_in(page);
await test_stream_message_edit(page);

View File

@ -1,28 +1,28 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function wait_for_tab(page, tab) {
async function wait_for_tab(page: Page, tab: string): Promise<void> {
const tab_slector = `#${CSS.escape(tab)}.tab-pane.active`;
await page.waitForSelector(tab_slector, {visible: true});
}
async function navigate_to(page, click_target, tab) {
async function navigate_to(page: Page, click_target: string, tab: string): Promise<void> {
console.log("Visiting #" + click_target);
await page.click(`a[href='#${CSS.escape(click_target)}']`);
await wait_for_tab(page, tab);
}
async function open_menu(page) {
async function open_menu(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown";
await page.waitForSelector(menu_selector, {visible: true});
await page.click(menu_selector);
}
async function navigate_to_settings(page) {
async function navigate_to_settings(page: Page): Promise<void> {
console.log("Navigating to settings");
await open_menu(page);
@ -36,7 +36,7 @@ async function navigate_to_settings(page) {
await page.click("#settings_page .content-wrapper .exit");
}
async function navigate_to_subscriptions(page) {
async function navigate_to_subscriptions(page: Page): Promise<void> {
console.log("Navigate to subscriptions");
await open_menu(page);
@ -51,7 +51,7 @@ async function navigate_to_subscriptions(page) {
await page.click("#subscription_overlay .exit");
}
async function test_reload_hash(page) {
async function test_reload_hash(page: Page): Promise<void> {
const initial_page_load_time = await page.evaluate(() => page_params.page_load_time);
console.log("initial load time: " + initial_page_load_time);
@ -70,7 +70,7 @@ async function test_reload_hash(page) {
assert.strictEqual(hash, initial_hash, "Hash not preserved.");
}
async function navigation_tests(page) {
async function navigation_tests(page: Page): Promise<void> {
await common.log_in(page);
await navigate_to_settings(page);

View File

@ -1,10 +1,10 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function submit_notifications_stream_settings(page) {
async function submit_notifications_stream_settings(page: Page): Promise<void> {
await page.waitForSelector('#org-submit-notifications[data-status="unsaved"]', {visible: true});
const save_button = "#org-submit-notifications";
@ -25,7 +25,7 @@ async function submit_notifications_stream_settings(page) {
await page.waitForSelector("#org-submit-notifications", {hidden: true});
}
async function test_change_new_stream_notifications_setting(page) {
async function test_change_new_stream_notifications_setting(page: Page): Promise<void> {
await page.click("#realm_notifications_stream_id_widget button.dropdown-toggle");
await page.waitForSelector("#realm_notifications_stream_id_widget ul.dropdown-menu", {
visible: true,
@ -41,7 +41,7 @@ async function test_change_new_stream_notifications_setting(page) {
await common.wait_for_text(page, verona_in_dropdown, "Verona");
await page.waitForSelector(verona_in_dropdown, {visible: true});
await page.evaluate((selector) => $(selector).trigger("click"), verona_in_dropdown);
await page.evaluate((selector: string) => $(selector).trigger("click"), verona_in_dropdown);
await submit_notifications_stream_settings(page);
@ -52,7 +52,7 @@ async function test_change_new_stream_notifications_setting(page) {
await submit_notifications_stream_settings(page);
}
async function test_change_signup_notifications_stream(page) {
async function test_change_signup_notifications_stream(page: Page): Promise<void> {
console.log('Changing signup notifications stream to Verona by filtering with "verona"');
await page.click("#id_realm_signup_notifications_stream_id > button.dropdown-toggle");
@ -79,7 +79,7 @@ async function test_change_signup_notifications_stream(page) {
await submit_notifications_stream_settings(page);
}
async function test_permissions_change_save_worked(page) {
async function test_permissions_change_save_worked(page: Page): Promise<void> {
const saved_status = '#org-submit-stream-permissions[data-status="saved"]';
await page.waitForSelector(saved_status, {
visible: true,
@ -87,7 +87,7 @@ async function test_permissions_change_save_worked(page) {
await page.waitForSelector(saved_status, {hidden: true});
}
async function submit_stream_permissions_change(page) {
async function submit_stream_permissions_change(page: Page): Promise<void> {
const save_button = "#org-submit-stream-permissions";
await page.waitForSelector(save_button, {visible: true});
assert.strictEqual(
@ -100,49 +100,49 @@ async function submit_stream_permissions_change(page) {
await test_permissions_change_save_worked(page);
}
async function test_set_create_streams_to_admins_only(page) {
async function test_set_create_streams_to_admins_only(page: Page): Promise<void> {
console.log("Test setting create streams policy to 'admins only'.");
await page.waitForSelector("#id_realm_create_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_create_stream_policy").val(2).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_set_create_streams_to_members_and_admins(page) {
async function test_set_create_streams_to_members_and_admins(page: Page): Promise<void> {
console.log("Test setting create streams policy to 'members and admins'.");
await page.waitForSelector("#id_realm_create_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_create_stream_policy").val(1).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_set_create_streams_policy_to_full_members(page) {
async function test_set_create_streams_policy_to_full_members(page: Page): Promise<void> {
console.log("Test setting create streams policy to 'full members'.");
await page.waitForSelector("#id_realm_create_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_create_stream_policy").val(3).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_set_invite_to_streams_policy_to_admins_only(page) {
async function test_set_invite_to_streams_policy_to_admins_only(page: Page): Promise<void> {
console.log("Test setting invite to streams policy to 'admins only'.");
await page.waitForSelector("#id_realm_invite_to_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_invite_to_stream_policy").val(2).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_set_invite_to_streams_policy_to_members_and_admins(page) {
async function test_set_invite_to_streams_policy_to_members_and_admins(page: Page): Promise<void> {
console.log("Test setting invite to streams policy to 'members and admins'.");
await page.waitForSelector("#id_realm_invite_to_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_invite_to_stream_policy").val(1).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_set_invite_to_streams_policy_to_full_members(page) {
async function test_set_invite_to_streams_policy_to_full_members(page: Page): Promise<void> {
console.log("Test setting invite to streams policy to 'full members'.");
await page.waitForSelector("#id_realm_invite_to_stream_policy", {visible: true});
await page.evaluate(() => $("#id_realm_invite_to_stream_policy").val(3).trigger("change"));
await submit_stream_permissions_change(page);
}
async function test_save_joining_organization_change_worked(page) {
async function test_save_joining_organization_change_worked(page: Page): Promise<void> {
const saved_status = '#org-submit-org-join[data-status="saved"]';
await page.waitForSelector(saved_status, {
visible: true,
@ -150,7 +150,7 @@ async function test_save_joining_organization_change_worked(page) {
await page.waitForSelector(saved_status, {hidden: true});
}
async function submit_joining_organization_change(page) {
async function submit_joining_organization_change(page: Page): Promise<void> {
const save_button = "#org-submit-org-join";
await page.waitForSelector(save_button, {visible: true});
assert.strictEqual(
@ -164,24 +164,28 @@ async function submit_joining_organization_change(page) {
await test_save_joining_organization_change_worked(page);
}
async function test_set_new_user_threshold_to_three_days(page) {
async function test_set_new_user_threshold_to_three_days(page: Page): Promise<void> {
console.log("Test setting new user threshold to three days.");
await page.waitForSelector("#id_realm_waiting_period_setting", {visible: true});
await page.evaluate(() => $("#id_realm_waiting_period_setting").val("three_days").trigger("change"));
await page.evaluate(() =>
$("#id_realm_waiting_period_setting").val("three_days").trigger("change"),
);
await submit_joining_organization_change(page);
}
async function test_set_new_user_threshold_to_N_days(page) {
async function test_set_new_user_threshold_to_N_days(page: Page): Promise<void> {
console.log("Test setting new user threshold to three days.");
await page.waitForSelector("#id_realm_waiting_period_setting", {visible: true});
await page.evaluate(() => $("#id_realm_waiting_period_setting").val("custom_days").trigger("change"));
await page.evaluate(() =>
$("#id_realm_waiting_period_setting").val("custom_days").trigger("change"),
);
const N = 10;
await page.evaluate((N) => $("#id_realm_waiting_period_threshold").val(N), N);
await page.evaluate((N: number) => $("#id_realm_waiting_period_threshold").val(N), N);
await submit_joining_organization_change(page);
}
async function test_organization_permissions(page) {
async function test_organization_permissions(page: Page): Promise<void> {
await page.click("li[data-section='organization-permissions']");
await test_set_create_streams_to_admins_only(page);
@ -196,11 +200,11 @@ async function test_organization_permissions(page) {
await test_set_new_user_threshold_to_N_days(page);
}
async function test_add_emoji(page) {
async function test_add_emoji(page: Page): Promise<void> {
await common.fill_form(page, "form.admin-emoji-form", {name: "zulip logo"});
const emoji_upload_handle = await page.$("#emoji_file_input");
await emoji_upload_handle.uploadFile("static/images/logo/zulip-icon-128x128.png");
await emoji_upload_handle!.uploadFile("static/images/logo/zulip-icon-128x128.png");
await page.click("#admin_emoji_submit");
const emoji_status = "div#admin-emoji-status";
@ -219,14 +223,14 @@ async function test_add_emoji(page) {
await page.waitForSelector("tr#emoji_zulip_logo img", {visible: true});
}
async function test_delete_emoji(page) {
async function test_delete_emoji(page: Page): Promise<void> {
await page.click("tr#emoji_zulip_logo button.delete");
// assert the emoji is deleted.
await page.waitForFunction(() => $("tr#emoji_zulip_logo").length === 0);
}
async function test_custom_realm_emoji(page) {
async function test_custom_realm_emoji(page: Page): Promise<void> {
await page.click("li[data-section='emoji-settings']");
await page.waitForSelector(".admin-emoji-form", {visible: true});
@ -234,8 +238,8 @@ async function test_custom_realm_emoji(page) {
await test_delete_emoji(page);
}
async function get_suggestions(page, str) {
await page.evaluate((str) => {
async function get_suggestions(page: Page, str: string): Promise<void> {
await page.evaluate((str: string) => {
$(".create_default_stream")
.trigger("focus")
.val(str)
@ -243,8 +247,8 @@ async function get_suggestions(page, str) {
}, str);
}
async function select_from_suggestions(page, item) {
await page.evaluate((item) => {
async function select_from_suggestions(page: Page, item: string): Promise<void> {
await page.evaluate((item: string) => {
const tah = $(".create_default_stream").data().typeahead;
tah.mouseenter({
currentTarget: $(`.typeahead:visible li:contains("${CSS.escape(item)}")`)[0],
@ -253,7 +257,11 @@ async function select_from_suggestions(page, item) {
}, item);
}
async function test_add_default_stream(page, stream_name, row) {
async function test_add_default_stream(
page: Page,
stream_name: string,
row: string,
): Promise<void> {
// It matches with all the stream names which has 'O' as a substring (Rome, Scotland, Verona
// etc). 'O' is used to make sure that it works even if there are multiple suggestions.
// Uppercase 'O' is used instead of the lowercase version to make sure that the suggestions
@ -265,28 +273,28 @@ async function test_add_default_stream(page, stream_name, row) {
await page.waitForSelector(row, {visible: true});
}
async function test_remove_default_stream(page, row) {
async function test_remove_default_stream(page: Page, row: string): Promise<void> {
await page.click(row + " button.remove-default-stream");
// assert row doesn't exist.
await page.waitForFunction((row) => $(row).length === 0, {}, row);
await page.waitForFunction((row: string) => $(row).length === 0, {}, row);
}
async function test_default_streams(page) {
async function test_default_streams(page: Page): Promise<void> {
await page.click("li[data-section='default-streams-list']");
await page.waitForSelector(".create_default_stream", {visible: true});
const stream_name = "Scotland";
const stream_id = await common.get_stream_id(page, stream_name);
const row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id)}']`;
const row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id.toString())}']`;
await test_add_default_stream(page, stream_name, row);
await test_remove_default_stream(page, row);
}
async function test_upload_realm_icon_image(page) {
async function test_upload_realm_icon_image(page: Page): Promise<void> {
const upload_handle = await page.$("#realm-icon-upload-widget .image_file_input");
await upload_handle.uploadFile("static/images/logo/zulip-icon-128x128.png");
await upload_handle!.uploadFile("static/images/logo/zulip-icon-128x128.png");
await page.waitForSelector("#realm-icon-upload-widget .upload-spinner-background", {
visible: true,
@ -300,14 +308,14 @@ async function test_upload_realm_icon_image(page) {
);
}
async function delete_realm_icon(page) {
async function delete_realm_icon(page: Page): Promise<void> {
await page.click("li[data-section='organization-profile']");
await page.click("#realm-icon-upload-widget .image-delete-button");
await page.waitForSelector("#realm-icon-upload-widget .image-delete-button", {visible: false});
}
async function test_organization_profile(page) {
async function test_organization_profile(page: Page): Promise<void> {
await page.click("li[data-section='organization-profile']");
const gravatar_selctor =
'#realm-icon-upload-widget .image-block[src^="https://secure.gravatar.com/avatar/"]';
@ -322,7 +330,7 @@ async function test_organization_profile(page) {
await page.waitForSelector(gravatar_selctor, {visible: true});
}
async function submit_default_user_settings(page) {
async function submit_default_user_settings(page: Page): Promise<void> {
assert.strictEqual(
await common.get_text_from_selector(page, "#org-submit-user-defaults"),
"Save changes",
@ -332,7 +340,7 @@ async function submit_default_user_settings(page) {
await page.waitForSelector(saved_status, {visible: false});
}
async function test_change_organization_default_language(page) {
async function test_change_organization_default_language(page: Page): Promise<void> {
console.log("Changing realm default language");
await page.click("li[data-section='organization-settings']");
await page.waitForSelector("#id_realm_default_language", {visible: true});
@ -341,7 +349,7 @@ async function test_change_organization_default_language(page) {
await submit_default_user_settings(page);
}
async function test_authentication_methods(page) {
async function test_authentication_methods(page: Page): Promise<void> {
await page.click("li[data-section='auth-methods']");
await page.waitForSelector(".method_row[data-method='Google'] input[type='checkbox'] + span", {
visible: true,
@ -364,11 +372,13 @@ async function test_authentication_methods(page) {
visible: true,
});
await page.waitForFunction(
() => !$(".method_row[data-method='Google'] input[type='checkbox']")[0].checked,
() =>
!($(".method_row[data-method='Google'] input[type='checkbox']")[0] as HTMLInputElement)
.checked,
);
}
async function admin_test(page) {
async function admin_test(page: Page): Promise<void> {
await common.log_in(page);
await common.manage_organization(page);

View File

@ -1,10 +1,10 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function test_mention(page) {
async function test_mention(page: Page): Promise<void> {
await common.log_in(page);
await page.keyboard.press("KeyC");
await page.waitForSelector("#compose", {visible: true});

View File

@ -1,14 +1,14 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
// These will be the row and edit form of the the custom profile we add.
const profile_field_row = "#admin_profile_fields_table tr:nth-last-child(2)";
const profile_field_form = "#admin_profile_fields_table tr:nth-last-child(1)";
async function test_add_new_profile_field(page) {
async function test_add_new_profile_field(page: Page): Promise<void> {
await page.waitForSelector(".admin-profile-field-form", {visible: true});
await common.fill_form(page, "form.admin-profile-field-form", {
name: "Teams",
@ -28,7 +28,7 @@ async function test_add_new_profile_field(page) {
);
}
async function test_edit_profile_field(page) {
async function test_edit_profile_field(page: Page): Promise<void> {
await page.click(`${profile_field_row} button.open-edit-form`);
await page.waitForSelector(`${profile_field_form} form.name-setting`, {visible: true});
await common.fill_form(page, `${profile_field_form} form.name-setting`, {
@ -44,7 +44,7 @@ async function test_edit_profile_field(page) {
);
}
async function test_delete_custom_profile_field(page) {
async function test_delete_custom_profile_field(page: Page): Promise<void> {
await page.click(`${profile_field_row} button.delete`);
await page.waitForSelector("#admin-profile-field-status img", {visible: true});
assert.strictEqual(
@ -53,7 +53,7 @@ async function test_delete_custom_profile_field(page) {
);
}
async function test_custom_profile(page) {
async function test_custom_profile(page: Page): Promise<void> {
await common.log_in(page);
await common.manage_organization(page);

View File

@ -1,10 +1,10 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function navigate_to_user_list(page) {
async function navigate_to_user_list(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown";
await page.waitForSelector(menu_selector, {visible: true});
await page.click(menu_selector);
@ -13,12 +13,12 @@ async function navigate_to_user_list(page) {
await page.click("li[data-section='user-list-admin']");
}
async function user_row(page, name) {
async function user_row(page: Page, name: string): Promise<string> {
const user_id = await common.get_user_id_from_name(page, name);
return `.user_row[data-user-id="${CSS.escape(user_id)}"]`;
return `.user_row[data-user-id="${CSS.escape(user_id.toString())}"]`;
}
async function test_deactivate_user(page) {
async function test_deactivate_user(page: Page): Promise<void> {
const cordelia_user_row = await user_row(page, "cordelia");
await page.waitForSelector(cordelia_user_row, {visible: true});
await page.waitForSelector(cordelia_user_row + " .fa-user-times");
@ -39,7 +39,7 @@ async function test_deactivate_user(page) {
await page.waitForSelector("#user-field-status", {hidden: true});
}
async function test_reactivate_user(page) {
async function test_reactivate_user(page: Page): Promise<void> {
let cordelia_user_row = await user_row(page, "cordelia");
await page.waitForSelector(cordelia_user_row + ".deactivated_user");
await page.waitForSelector(cordelia_user_row + " .fa-user-plus");
@ -51,7 +51,7 @@ async function test_reactivate_user(page) {
await page.waitForSelector("#user-field-status", {hidden: true});
}
async function test_deactivated_users_section(page) {
async function test_deactivated_users_section(page: Page): Promise<void> {
const cordelia_user_row = await user_row(page, "cordelia");
await test_deactivate_user(page);
@ -72,7 +72,7 @@ async function test_deactivated_users_section(page) {
);
}
async function test_bot_deactivation_and_reactivation(page) {
async function test_bot_deactivation_and_reactivation(page: Page): Promise<void> {
await page.click("li[data-section='bot-list-admin']");
const default_bot_user_row = await user_row(page, "Zulip Default Bot");
@ -87,7 +87,7 @@ async function test_bot_deactivation_and_reactivation(page) {
await page.waitForSelector(default_bot_user_row + " .fa-user-times");
}
async function user_deactivation_test(page) {
async function user_deactivation_test(page: Page): Promise<void> {
await common.log_in(page);
await navigate_to_user_list(page);
await test_deactivate_user(page);

View File

@ -1,22 +1,22 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function wait_for_drafts_to_dissapear(page) {
async function wait_for_drafts_to_dissapear(page: Page): Promise<void> {
await page.waitForFunction(
() => $("#draft_overlay").length === 0 || $("#draft_overlay").css("opacity") === "0",
);
}
async function wait_for_drafts_to_appear(page) {
async function wait_for_drafts_to_appear(page: Page): Promise<void> {
await page.waitForFunction(
() => $("#draft_overlay").length === 1 && $("#draft_overlay").css("opacity") === "1",
);
}
async function get_drafts_count(page) {
async function get_drafts_count(page: Page): Promise<number> {
return await page.$$eval(".draft-row", (drafts) => drafts.length);
}
@ -24,7 +24,7 @@ const drafts_button = ".compose_drafts_button";
const drafts_overlay = "#draft_overlay";
const drafts_button_in_compose = "#below-compose-content .drafts-link";
async function test_empty_drafts(page) {
async function test_empty_drafts(page: Page): Promise<void> {
await page.waitForSelector(drafts_button, {visible: true});
await page.click(drafts_button);
@ -36,7 +36,7 @@ async function test_empty_drafts(page) {
await wait_for_drafts_to_dissapear(page);
}
async function create_stream_message_draft(page) {
async function create_stream_message_draft(page: Page): Promise<void> {
console.log("Creating Stream Message Draft");
await page.keyboard.press("KeyC");
await page.waitForSelector("#stream-message", {visible: true});
@ -48,7 +48,7 @@ async function create_stream_message_draft(page) {
await page.click("#compose_close");
}
async function create_private_message_draft(page) {
async function create_private_message_draft(page: Page): Promise<void> {
console.log("Creating private message draft");
await page.keyboard.press("KeyX");
await page.waitForSelector("#private_message_recipient", {visible: true});
@ -58,7 +58,7 @@ async function create_private_message_draft(page) {
await page.click("#compose_close");
}
async function open_compose_markdown_preview(page) {
async function open_compose_markdown_preview(page: Page): Promise<void> {
const new_topic_button = "#left_bar_compose_stream_button_big";
await page.waitForSelector(new_topic_button, {visible: true});
await page.click(new_topic_button);
@ -68,14 +68,14 @@ async function open_compose_markdown_preview(page) {
await page.click(markdown_preview_button);
}
async function open_drafts_through_compose(page) {
async function open_drafts_through_compose(page: Page): Promise<void> {
await open_compose_markdown_preview(page);
await page.waitForSelector(drafts_button_in_compose, {visible: true});
await page.click(drafts_button_in_compose);
await wait_for_drafts_to_appear(page);
}
async function test_previously_created_drafts_rendered(page) {
async function test_previously_created_drafts_rendered(page: Page): Promise<void> {
const drafts_count = await get_drafts_count(page);
assert.strictEqual(drafts_count, 2, "Drafts improperly loaded.");
assert.strictEqual(
@ -109,7 +109,7 @@ async function test_previously_created_drafts_rendered(page) {
);
}
async function test_restore_message_draft(page) {
async function test_restore_message_draft(page: Page): Promise<void> {
console.log("Restoring Stream Message Draft");
await page.click("#drafts_table .message_row:not(.private-message) .restore-draft");
await wait_for_drafts_to_dissapear(page);
@ -127,7 +127,7 @@ async function test_restore_message_draft(page) {
);
}
async function edit_stream_message_draft(page) {
async function edit_stream_message_draft(page: Page): Promise<void> {
await common.fill_form(page, "form#send_message_form", {
stream_message_recipient_stream: "all",
stream_message_recipient_topic: "tests",
@ -136,7 +136,7 @@ async function edit_stream_message_draft(page) {
await page.click("#compose_close");
}
async function test_edited_draft_message(page) {
async function test_edited_draft_message(page: Page): Promise<void> {
await page.waitForSelector(drafts_button, {visible: true});
await page.click(drafts_button);
@ -161,7 +161,7 @@ async function test_edited_draft_message(page) {
);
}
async function test_restore_private_message_draft(page) {
async function test_restore_private_message_draft(page: Page): Promise<void> {
console.log("Restoring private message draft.");
await page.click("#drafts_table .message_row.private-message .restore-draft");
await wait_for_drafts_to_dissapear(page);
@ -180,7 +180,7 @@ async function test_restore_private_message_draft(page) {
await page.click("#compose_close");
}
async function test_delete_draft(page) {
async function test_delete_draft(page: Page): Promise<void> {
console.log("Deleting draft");
await page.waitForSelector(drafts_button, {visible: true});
await page.click(drafts_button);
@ -194,7 +194,7 @@ async function test_delete_draft(page) {
await page.click("body");
}
async function test_save_draft_by_reloading(page) {
async function test_save_draft_by_reloading(page: Page): Promise<void> {
console.log("Saving draft by reloading.");
await page.keyboard.press("KeyX");
await page.waitForSelector("#private-message", {visible: true});
@ -229,7 +229,7 @@ async function test_save_draft_by_reloading(page) {
);
}
async function test_delete_draft_on_sending(page) {
async function test_delete_draft_on_sending(page: Page): Promise<void> {
await page.click("#drafts_table .message_row.private-message .restore-draft");
await wait_for_drafts_to_dissapear(page);
await page.waitForSelector("#private-message", {visible: true});
@ -245,7 +245,7 @@ async function test_delete_draft_on_sending(page) {
await page.waitForSelector("#drafts_table .message_row.private-message", {hidden: true});
}
async function drafts_test(page) {
async function drafts_test(page: Page): Promise<void> {
await common.log_in(page);
await test_empty_drafts(page);

View File

@ -1,8 +1,8 @@
"use strict";
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function click_delete_and_return_last_msg_id(page) {
async function click_delete_and_return_last_msg_id(page: Page): Promise<string | undefined> {
return await page.evaluate(() => {
const msg = $("#zhome .message_row").last();
msg.find(".info").trigger("click");
@ -11,7 +11,7 @@ async function click_delete_and_return_last_msg_id(page) {
});
}
async function delete_message_test(page) {
async function delete_message_test(page: Page): Promise<void> {
await common.log_in(page);
const messages_quantitiy = await page.evaluate(() => $("#zhome .message_row").length);
const last_message_id = await click_delete_and_return_last_msg_id(page);
@ -24,12 +24,12 @@ async function delete_message_test(page) {
await page.waitForSelector("#do_delete_message_button", {hidden: true});
await page.waitForFunction(
(expected_length) => $("#zhome .message_row").length === expected_length,
(expected_length: number) => $("#zhome .message_row").length === expected_length,
{},
messages_quantitiy - 1,
);
await page.waitForSelector(`#${CSS.escape(last_message_id)}`, {hidden: true});
await page.waitForSelector(`#${CSS.escape(last_message_id!)}`, {hidden: true});
await page.waitForSelector("#do_delete_message_spinner .loading_indicator_spinner", {
hidden: true,
});

View File

@ -1,13 +1,17 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function copy_messages(page, start_message, end_message) {
async function copy_messages(
page: Page,
start_message: string,
end_message: string,
): Promise<string[]> {
return await page.evaluate(
(start_message, end_message) => {
function get_message_node(message) {
(start_message: string, end_message: string) => {
function get_message_node(message: string): Element {
return $(`.message_row .message_content:contains("${CSS.escape(message)}")`).get(0);
}
@ -15,8 +19,8 @@ async function copy_messages(page, start_message, end_message) {
const selectedRange = document.createRange();
selectedRange.setStartAfter(get_message_node(start_message));
selectedRange.setEndBefore(get_message_node(end_message));
window.getSelection().removeAllRanges();
window.getSelection().addRange(selectedRange);
window.getSelection()!.removeAllRanges();
window.getSelection()!.addRange(selectedRange);
// Remove existing copy/paste divs, which may linger from the previous
// example. (The code clears these out with a zero-second timeout, which
@ -31,32 +35,32 @@ async function copy_messages(page, start_message, end_message) {
return temp_div
.children("p")
.get()
.map((p) => p.textContent);
.map((p) => p.textContent!);
},
start_message,
end_message,
);
}
async function test_copying_first_message_from_topic(page) {
async function test_copying_first_message_from_topic(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test C", "copy paste test C");
const expected_copied_lines = [];
const expected_copied_lines: string[] = [];
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_last_message_from_topic(page) {
async function test_copying_last_message_from_topic(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test E", "copy paste test E");
const expected_copied_lines = [];
const expected_copied_lines: string[] = [];
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_first_two_messages_from_topic(page) {
async function test_copying_first_two_messages_from_topic(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test C", "copy paste test D");
const expected_copied_lines = ["Desdemona: copy paste test C", "Desdemona: copy paste test D"];
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_all_messages_from_topic(page) {
async function test_copying_all_messages_from_topic(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test C", "copy paste test E");
const expected_copied_lines = [
"Desdemona: copy paste test C",
@ -66,7 +70,7 @@ async function test_copying_all_messages_from_topic(page) {
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_last_from_prev_first_from_next(page) {
async function test_copying_last_from_prev_first_from_next(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test B", "copy paste test C");
const expected_copied_lines = [
"Verona > copy-paste-topic #1 Today",
@ -77,7 +81,7 @@ async function test_copying_last_from_prev_first_from_next(page) {
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_last_from_prev_all_from_next(page) {
async function test_copying_last_from_prev_all_from_next(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test B", "copy paste test E");
const expected_copied_lines = [
"Verona > copy-paste-topic #1 Today",
@ -90,7 +94,7 @@ async function test_copying_last_from_prev_all_from_next(page) {
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_all_from_prev_first_from_next(page) {
async function test_copying_all_from_prev_first_from_next(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test A", "copy paste test C");
const expected_copied_lines = [
"Verona > copy-paste-topic #1 Today",
@ -102,7 +106,7 @@ async function test_copying_all_from_prev_first_from_next(page) {
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function test_copying_messages_from_several_topics(page) {
async function test_copying_messages_from_several_topics(page: Page): Promise<void> {
const actual_copied_lines = await copy_messages(page, "copy paste test B", "copy paste test F");
const expected_copied_lines = [
"Verona > copy-paste-topic #1 Today",
@ -117,7 +121,7 @@ async function test_copying_messages_from_several_topics(page) {
assert.deepStrictEqual(actual_copied_lines, expected_copied_lines);
}
async function copy_paste_test(page) {
async function copy_paste_test(page: Page): Promise<void> {
await common.log_in(page);
await common.send_multiple_messages(page, [

View File

@ -1,10 +1,10 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const common = require("../puppeteer_lib/common");
import common from "../puppeteer_lib/common";
async function test_add_linkifier(page) {
async function test_add_linkifier(page: Page): Promise<void> {
await page.waitForSelector(".admin-filter-form", {visible: true});
await common.fill_form(page, "form.admin-filter-form", {
pattern: "#(?P<id>[0-9]+)",
@ -31,12 +31,12 @@ async function test_add_linkifier(page) {
);
}
async function test_delete_linkifier(page) {
async function test_delete_linkifier(page: Page): Promise<void> {
await page.click(".filter_row button");
await page.waitForSelector(".filter_row", {hidden: true});
}
async function test_invalid_linkifier_pattern(page) {
async function test_invalid_linkifier_pattern(page: Page): Promise<void> {
await page.waitForSelector(".admin-filter-form", {visible: true});
await common.fill_form(page, "form.admin-filter-form", {
pattern: "a$",
@ -51,7 +51,7 @@ async function test_invalid_linkifier_pattern(page) {
);
}
async function realm_linkifier_test(page) {
async function realm_linkifier_test(page: Page): Promise<void> {
await common.log_in(page);
await common.manage_organization(page);
await page.click("li[data-section='filter-settings']");

View File

@ -1,23 +1,23 @@
"use strict";
import {strict as assert} from "assert";
const {strict: assert} = require("assert");
import type {Page} from "puppeteer";
const {test_credentials} = require("../../var/puppeteer/test_credentials");
const common = require("../puppeteer_lib/common");
import {test_credentials} from "../../var/puppeteer/test_credentials";
import common from "../puppeteer_lib/common";
const OUTGOING_WEBHOOK_BOT_TYPE = "3";
const GENERIC_BOT_TYPE = "1";
const zuliprc_regex = /^data:application\/octet-stream;charset=utf-8,\[api]\nemail=.+\nkey=.+\nsite=.+\n$/;
async function get_decoded_url_in_selector(page, selector) {
async function get_decoded_url_in_selector(page: Page, selector: string): Promise<string> {
return await page.evaluate(
(selector) => decodeURIComponent($(selector).attr("href")),
(selector: string) => decodeURIComponent($(selector).attr("href")!),
selector,
);
}
async function open_settings(page) {
async function open_settings(page: Page): Promise<void> {
const menu_selector = "#settings-dropdown";
await page.waitForSelector(menu_selector, {visible: true});
await page.click(menu_selector);
@ -31,7 +31,7 @@ async function open_settings(page) {
assert(page_url.includes("/#settings/"), `Page url: ${page_url} does not contain /#settings/`);
}
async function test_change_full_name(page) {
async function test_change_full_name(page: Page): Promise<void> {
await page.click("#change_full_name");
const change_full_name_button_selector = "#change_full_name_button";
@ -39,7 +39,7 @@ async function test_change_full_name(page) {
const full_name_input_selector = 'input[name="full_name"]';
await page.$eval(full_name_input_selector, (el) => {
el.value = "";
(el as HTMLInputElement).value = "";
});
await page.waitForFunction(() => $(":focus").attr("id") === "change_full_name_modal");
await page.type(full_name_input_selector, "New name");
@ -47,7 +47,7 @@ async function test_change_full_name(page) {
await page.waitForFunction(() => $("#change_full_name").text().trim() === "New name");
}
async function test_change_password(page) {
async function test_change_password(page: Page): Promise<void> {
await page.click("#change_password");
const change_password_button_selector = "#change_password_button";
@ -62,7 +62,7 @@ async function test_change_password(page) {
await page.waitForFunction(() => $("#change_password_modal").attr("aria-hidden") === "true");
}
async function test_get_api_key(page) {
async function test_get_api_key(page: Page): Promise<void> {
const show_change_api_key_selector = "#api_key_button";
await page.click(show_change_api_key_selector);
@ -85,7 +85,7 @@ async function test_get_api_key(page) {
await page.click("#api_key_modal .close");
}
async function test_webhook_bot_creation(page) {
async function test_webhook_bot_creation(page: Page): Promise<void> {
await common.fill_form(page, "#create_bot_form", {
bot_name: "Bot 1",
bot_short_name: "1",
@ -111,7 +111,7 @@ async function test_webhook_bot_creation(page) {
);
}
async function test_normal_bot_creation(page) {
async function test_normal_bot_creation(page: Page): Promise<void> {
await page.click(".add-a-new-bot-tab");
await page.waitForSelector("#create_bot_button", {visible: true});
@ -134,7 +134,7 @@ async function test_normal_bot_creation(page) {
assert(zuliprc_regex.test(zuliprc_decoded_url), "Incorrect zuliprc format for bot.");
}
async function test_botserverrc(page) {
async function test_botserverrc(page: Page): Promise<void> {
await page.click("#download_botserverrc");
await page.waitForSelector('#download_botserverrc[href^="data:application"]', {visible: true});
const botserverrc_decoded_url = await get_decoded_url_in_selector(
@ -145,7 +145,7 @@ async function test_botserverrc(page) {
assert(botserverrc_regex.test(botserverrc_decoded_url), "Incorrect botserverrc format.");
}
async function test_edit_bot_form(page) {
async function test_edit_bot_form(page: Page): Promise<void> {
const bot1_email = "1-bot@zulip.testserver";
const bot1_edit_btn = `.open_edit_bot_form[data-email="${CSS.escape(bot1_email)}"]`;
await page.click(bot1_edit_btn);
@ -164,14 +164,14 @@ async function test_edit_bot_form(page) {
const bot1_name_selector = `.details:has(${bot1_edit_btn}) .name`;
await page.waitForFunction(
(bot1_name_selector) => $(bot1_name_selector).text() !== "Bot 1",
(bot1_name_selector: string) => $(bot1_name_selector).text() !== "Bot 1",
{},
bot1_name_selector,
);
assert.strictEqual(await common.get_text_from_selector(page, bot1_name_selector), "Bot one");
}
async function test_your_bots_section(page) {
async function test_your_bots_section(page: Page): Promise<void> {
await page.click('[data-section="your-bots"]');
await test_webhook_bot_creation(page);
await test_normal_bot_creation(page);
@ -181,55 +181,61 @@ async function test_your_bots_section(page) {
const alert_word_status_selector = "#alert_word_status";
async function add_alert_word(page, word) {
async function add_alert_word(page: Page, word: string): Promise<void> {
await page.type("#create_alert_word_name", word);
await page.click("#create_alert_word_button");
}
async function check_alert_word_added(page, word) {
async function check_alert_word_added(page: Page, word: string): Promise<void> {
const added_alert_word_selector = `.alert-word-item[data-word='${CSS.escape(word)}']`;
await page.waitForSelector(added_alert_word_selector, {visible: true});
}
async function get_alert_words_status_text(page) {
async function get_alert_words_status_text(page: Page): Promise<string> {
await page.waitForSelector(alert_word_status_selector, {visible: true});
const status_text = await common.get_text_from_selector(page, ".alert_word_status_text");
return status_text;
}
async function close_alert_words_status(page) {
async function close_alert_words_status(page: Page): Promise<void> {
const status_close_btn = ".close-alert-word-status";
await page.click(status_close_btn);
await page.waitForSelector(alert_word_status_selector, {hidden: true});
}
async function test_and_close_alert_word_added_successfully_status(page, word) {
async function test_and_close_alert_word_added_successfully_status(
page: Page,
word: string,
): Promise<void> {
const status_text = await get_alert_words_status_text(page);
assert.strictEqual(status_text, `Alert word "${word}" added successfully!`);
await close_alert_words_status(page);
}
async function test_duplicate_alert_words_cannot_be_added(page, duplicate_word) {
async function test_duplicate_alert_words_cannot_be_added(
page: Page,
duplicate_word: string,
): Promise<void> {
await add_alert_word(page, duplicate_word);
const status_text = await get_alert_words_status_text(page);
assert.strictEqual(status_text, "Alert word already exists!");
await close_alert_words_status(page);
}
async function delete_alert_word(page, word) {
async function delete_alert_word(page: Page, word: string): Promise<void> {
const delete_btn_selector = `.remove-alert-word[data-word="${CSS.escape(word)}"]`;
await page.click(delete_btn_selector);
await page.waitForSelector(delete_btn_selector, {hidden: true});
}
async function test_alert_word_deletion(page, word) {
async function test_alert_word_deletion(page: Page, word: string): Promise<void> {
await delete_alert_word(page, word);
const status_text = await get_alert_words_status_text(page);
assert.strictEqual(status_text, "Alert word removed successfully!");
await close_alert_words_status(page);
}
async function test_alert_words_section(page) {
async function test_alert_words_section(page: Page): Promise<void> {
await page.click('[data-section="alert-words"]');
const word = "puppeteer";
await add_alert_word(page, word);
@ -239,7 +245,7 @@ async function test_alert_words_section(page) {
await test_alert_word_deletion(page, word);
}
async function change_language(page, language_data_code) {
async function change_language(page: Page, language_data_code: string): Promise<void> {
await page.waitForSelector("#default_language", {visible: true});
await page.click("#default_language");
await page.waitForSelector("#default_language_modal", {visible: true});
@ -247,19 +253,19 @@ async function change_language(page, language_data_code) {
await page.click(language_selector);
}
async function check_language_setting_status(page) {
async function check_language_setting_status(page: Page): Promise<void> {
const language_setting_status_selector = "#language-settings-status";
await page.waitForSelector(language_setting_status_selector, {visible: true});
const status_text = "Saved. Please reload for the change to take effect.";
await page.waitForFunction(
(selector, status) => $(selector).text() === status,
(selector: string, status: string) => $(selector).text() === status,
{},
language_setting_status_selector,
status_text,
);
}
async function assert_language_changed_to_chinese(page) {
async function assert_language_changed_to_chinese(page: Page): Promise<void> {
await page.waitForSelector("#default_language", {visible: true});
const default_language = await common.get_text_from_selector(page, "#default_language");
assert.strictEqual(
@ -269,7 +275,7 @@ async function assert_language_changed_to_chinese(page) {
);
}
async function test_i18n_language_precedence(page) {
async function test_i18n_language_precedence(page: Page): Promise<void> {
const settings_url_for_german = "http://zulip.zulipdev.com:9981/de/#settings";
await page.goto(settings_url_for_german);
await page.waitForSelector("#settings-change-box", {visible: true});
@ -277,7 +283,7 @@ async function test_i18n_language_precedence(page) {
assert.strictEqual(page_language_code, "de");
}
async function test_default_language_setting(page) {
async function test_default_language_setting(page: Page): Promise<void> {
const display_settings_section = '[data-section="display-settings"]';
await page.click(display_settings_section);
@ -304,7 +310,7 @@ async function test_default_language_setting(page) {
await page.waitForSelector("#default_language", {visible: true});
}
async function test_notifications_section(page) {
async function test_notifications_section(page: Page): Promise<void> {
await page.click('[data-section="notifications"]');
// At the beginning, "PMs, mentions, and alerts"(checkbox name=enable_sounds) audio will be on
// and "Streams"(checkbox name=enable_stream_audible_notifications) audio will be off by default.
@ -331,7 +337,7 @@ async function test_notifications_section(page) {
*/
}
async function settings_tests(page) {
async function settings_tests(page: Page): Promise<void> {
await common.log_in(page);
await open_settings(page);
await test_change_full_name(page);

View File

@ -146,3 +146,7 @@ declare let user_events: any;
declare let user_groups: any;
declare let user_pill: any;
declare let widgetize: any;
interface JQuery {
expectOne(): JQuery;
}

View File

@ -100,7 +100,9 @@ def find_js_test_files(test_dir: str, files: Iterable[str]) -> List[str]:
test_files.append(os.path.abspath(file))
if not test_files:
test_files = sorted(glob.glob(os.path.join(test_dir, "*.js")))
test_files = sorted(
glob.glob(os.path.join(test_dir, "*.ts")) + glob.glob(os.path.join(test_dir, "*.js"))
)
return test_files

View File

@ -61,7 +61,14 @@ def run_tests(files: Iterable[str], external_host: str) -> None:
current_test_num = test_number
for test_file in test_files[test_number:]:
test_name = os.path.basename(test_file)
cmd = ["node", "--unhandled-rejections=strict", test_file]
cmd = [
"node",
"--unhandled-rejections=strict",
os.path.join(ZULIP_PATH, "node_modules/.bin/ts-node"),
"--script-mode",
"--transpile-only",
test_file,
]
print(
"\n\n===================== {}\nRunning {}\n\n".format(
test_name, " ".join(map(shlex.quote, cmd))

6
var/puppeteer/test_credentials.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export const test_credentials: {
default_user: {
username: string;
password: string;
};
};