Add an end-to-end test for stickers (#7733)
* Add an end-to-end test for stickers * More logs on login * Wait for spinners to go away * Factor out spinner waiting as it seems useful * Move stickers to the end * More waiting * When all else fails... add sleeps * Waiting for the server picker to appear seems to work..? * Typos Co-authored-by: J. Ryan Stinnett <jryans@gmail.com> * remove commented code from registration usecase Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
parent
84e15fa148
commit
5fe8442f44
8 changed files with 376 additions and 4 deletions
|
@ -315,6 +315,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
|||
case LoginField.Email:
|
||||
classes.error = this.props.loginIncorrect && !this.props.username;
|
||||
return <EmailField
|
||||
id="mx_LoginForm_email"
|
||||
className={classNames(classes)}
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
autoComplete="email"
|
||||
|
@ -333,6 +334,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
|||
case LoginField.MatrixId:
|
||||
classes.error = this.props.loginIncorrect && !this.props.username;
|
||||
return <Field
|
||||
id="mx_LoginForm_username"
|
||||
className={classNames(classes)}
|
||||
name="username" // make it a little easier for browser's remember-password
|
||||
autoComplete="username"
|
||||
|
@ -360,6 +362,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
|||
/>;
|
||||
|
||||
return <Field
|
||||
id="mx_LoginForm_phone"
|
||||
className={classNames(classes)}
|
||||
name="phoneNumber"
|
||||
autoComplete="tel-national"
|
||||
|
@ -447,6 +450,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
|||
{ loginType }
|
||||
{ loginField }
|
||||
<Field
|
||||
id="mx_LoginForm_password"
|
||||
className={pwFieldClass}
|
||||
autoComplete="password"
|
||||
type="password"
|
||||
|
|
|
@ -22,10 +22,10 @@ import { approveConsent } from './consent';
|
|||
import { Credentials } from "./creator";
|
||||
|
||||
interface RoomOptions {
|
||||
invite: string;
|
||||
public: boolean;
|
||||
topic: string;
|
||||
dm: boolean;
|
||||
invite?: string;
|
||||
public?: boolean;
|
||||
topic?: string;
|
||||
dm?: boolean;
|
||||
}
|
||||
|
||||
export class RestSession {
|
||||
|
|
|
@ -25,6 +25,7 @@ import { RestSessionCreator } from "./rest/creator";
|
|||
import { RestMultiSession } from "./rest/multi";
|
||||
import { spacesScenarios } from './scenarios/spaces';
|
||||
import { RestSession } from "./rest/session";
|
||||
import { stickerScenarios } from './scenarios/sticker';
|
||||
|
||||
export async function scenario(createSession: (s: string) => Promise<ElementSession>,
|
||||
restCreator: RestSessionCreator): Promise<void> {
|
||||
|
@ -51,6 +52,15 @@ export async function scenario(createSession: (s: string) => Promise<ElementSess
|
|||
await lazyLoadingScenarios(alice, bob, charlies);
|
||||
// do spaces scenarios last as the rest of the tests may get confused by spaces
|
||||
await spacesScenarios(alice, bob);
|
||||
|
||||
// we spawn another session for stickers, partially because it involves injecting
|
||||
// a custom sticker picker widget for the account, although mostly because for these
|
||||
// tests to scale, they probably need to be split up more, which means running each
|
||||
// scenario with it's own session (and will make it easier to find relevant logs),
|
||||
// so lets move in this direction (although at some point we'll also need to start
|
||||
// closing them as we go rather than leaving them all open until the end).
|
||||
const stickerSession = await createSession("sally");
|
||||
await stickerScenarios("sally", "ilikestickers", stickerSession, restCreator);
|
||||
}
|
||||
|
||||
async function createRestUsers(restCreator: RestSessionCreator): Promise<RestMultiSession> {
|
||||
|
|
143
test/end-to-end-tests/src/scenarios/sticker.ts
Normal file
143
test/end-to-end-tests/src/scenarios/sticker.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as http from "http";
|
||||
import { AddressInfo } from "net";
|
||||
|
||||
import { RestSessionCreator } from "../rest/creator";
|
||||
import { ElementSession } from "../session";
|
||||
import { login } from "../usecases/login";
|
||||
import { selectRoom } from "../usecases/select-room";
|
||||
import { sendSticker } from "../usecases/send-sticker";
|
||||
|
||||
const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker";
|
||||
const ROOM_NAME_1 = "Sticker Test";
|
||||
const ROOM_NAME_2 = "Sticker Test Two";
|
||||
const STICKER_MESSAGE = JSON.stringify({
|
||||
action: "m.sticker",
|
||||
api: "fromWidget",
|
||||
data: {
|
||||
name: "teststicker",
|
||||
description: "Test Sticker",
|
||||
file: "test.png",
|
||||
content: {
|
||||
body: "Test Sticker",
|
||||
msgtype: "m.sticker",
|
||||
url: "mxc://somewhere",
|
||||
},
|
||||
},
|
||||
requestId: "1",
|
||||
widgetId: STICKER_PICKER_WIDGET_ID,
|
||||
});
|
||||
const WIDGET_HTML = `
|
||||
<html>
|
||||
<head>
|
||||
<title>Fake Sticker Picker</title>
|
||||
<script>
|
||||
window.onmessage = ev => {
|
||||
if (ev.data.action === 'capabilities') {
|
||||
window.parent.postMessage(Object.assign({
|
||||
response: {
|
||||
capabilities: ["m.sticker"]
|
||||
},
|
||||
}, ev.data), '*');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button name="Send" id="sendsticker">Press for sticker</button>
|
||||
<script>
|
||||
document.getElementById('sendsticker').onclick = () => {
|
||||
window.parent.postMessage(${STICKER_MESSAGE}, '*')
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
class WidgetServer {
|
||||
private server: http.Server = null;
|
||||
|
||||
start() {
|
||||
this.server = http.createServer(this.onRequest);
|
||||
this.server.listen();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
get port(): number {
|
||||
return (this.server.address()as AddressInfo).port;
|
||||
}
|
||||
|
||||
onRequest = (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
res.writeHead(200);
|
||||
res.end(WIDGET_HTML);
|
||||
};
|
||||
}
|
||||
|
||||
export async function stickerScenarios(
|
||||
username: string, password: string,
|
||||
session: ElementSession, restCreator: RestSessionCreator,
|
||||
): Promise<void> {
|
||||
console.log(" making account to test stickers");
|
||||
|
||||
const creds = await restCreator.createSession(username, password);
|
||||
|
||||
// we make the room here which also approves the consent stuff
|
||||
// (besides, we test creating rooms elsewhere: no need to do so again)
|
||||
await creds.createRoom(ROOM_NAME_1, {});
|
||||
await creds.createRoom(ROOM_NAME_2, {});
|
||||
|
||||
console.log(" injecting fake sticker picker");
|
||||
|
||||
const widgetServer = new WidgetServer();
|
||||
widgetServer.start();
|
||||
|
||||
const stickerPickerUrl = `http://localhost:${widgetServer.port}/`;
|
||||
|
||||
await creds.put(`/user/${encodeURIComponent(creds.userId())}/account_data/m.widgets`, {
|
||||
"fake_sticker_picker": {
|
||||
content: {
|
||||
type: "m.stickerpicker",
|
||||
name: "Fake Stickers",
|
||||
url: stickerPickerUrl,
|
||||
},
|
||||
id: STICKER_PICKER_WIDGET_ID,
|
||||
},
|
||||
});
|
||||
|
||||
await login(session, username, password, session.hsUrl);
|
||||
|
||||
session.log.startGroup(`can send a sticker`);
|
||||
await selectRoom(session, ROOM_NAME_1);
|
||||
await sendSticker(session);
|
||||
session.log.endGroup();
|
||||
|
||||
// switch to another room & send another one
|
||||
session.log.startGroup(`can send a sticker to another room`);
|
||||
|
||||
const navPromise = session.page.waitForNavigation();
|
||||
await selectRoom(session, ROOM_NAME_2);
|
||||
await navPromise;
|
||||
|
||||
await sendSticker(session);
|
||||
session.log.endGroup();
|
||||
|
||||
widgetServer.stop();
|
||||
}
|
|
@ -198,6 +198,10 @@ export class ElementSession {
|
|||
this.page.off('request', onRequest);
|
||||
}
|
||||
|
||||
public async waitNoSpinner(): Promise<void> {
|
||||
await this.page.waitForSelector(".mx_Spinner", { hidden: true });
|
||||
}
|
||||
|
||||
public goto(url: string): Promise<puppeteer.HTTPResponse> {
|
||||
return this.page.goto(url);
|
||||
}
|
||||
|
|
91
test/end-to-end-tests/src/usecases/login.ts
Normal file
91
test/end-to-end-tests/src/usecases/login.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { strict as assert } from 'assert';
|
||||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function login(
|
||||
session: ElementSession,
|
||||
username: string, password: string,
|
||||
homeserver: string,
|
||||
): Promise<void> {
|
||||
session.log.startGroup("logs in");
|
||||
session.log.step("Navigates to login page");
|
||||
|
||||
const navPromise = session.page.waitForNavigation();
|
||||
await session.goto(session.url('/#/login'));
|
||||
await navPromise;
|
||||
session.log.done();
|
||||
|
||||
// for reasons I still don't fully understand, this seems to be flakey
|
||||
// such that when it's trying to click on 'mx_ServerPicker_change',
|
||||
// it ends up clicking instead on the dropdown for username / email / phone.
|
||||
// Waiting for the serverpicker to appear before proceeding seems to make
|
||||
// it reliable...
|
||||
await session.query('.mx_ServerPicker');
|
||||
|
||||
// wait until no spinners visible
|
||||
await session.waitNoSpinner();
|
||||
|
||||
// change the homeserver by clicking the advanced section
|
||||
if (homeserver) {
|
||||
session.log.step("Clicks button to change homeserver");
|
||||
const changeButton = await session.query('.mx_ServerPicker_change');
|
||||
await changeButton.click();
|
||||
session.log.done();
|
||||
|
||||
session.log.step("Enters homeserver");
|
||||
const hsInputField = await session.query('.mx_ServerPickerDialog_otherHomeserver');
|
||||
await session.replaceInputText(hsInputField, homeserver);
|
||||
session.log.done();
|
||||
|
||||
session.log.step("Clicks next");
|
||||
const nextButton = await session.query('.mx_ServerPickerDialog_continue');
|
||||
// accept homeserver
|
||||
await nextButton.click();
|
||||
session.log.done();
|
||||
}
|
||||
// Delay required because of local race condition on macOS
|
||||
// Where the form is not query-able despite being present in the DOM
|
||||
await session.delay(100);
|
||||
|
||||
session.log.step("Fills in login form");
|
||||
//fill out form
|
||||
const usernameField = await session.query("#mx_LoginForm_username");
|
||||
const passwordField = await session.query("#mx_LoginForm_password");
|
||||
await session.replaceInputText(usernameField, username);
|
||||
await session.replaceInputText(passwordField, password);
|
||||
session.log.done();
|
||||
|
||||
session.log.step("Clicks login");
|
||||
const loginButton = await session.query('.mx_Login_submit');
|
||||
await loginButton.focus();
|
||||
//check no errors
|
||||
const errorText = await session.tryGetInnertext('.mx_Login_error');
|
||||
assert.strictEqual(errorText, null);
|
||||
//submit form
|
||||
//await page.screenshot({path: "beforesubmit.png", fullPage: true});
|
||||
await loginButton.click();
|
||||
session.log.done();
|
||||
|
||||
const foundHomeUrl = await session.poll(async () => {
|
||||
const url = session.page.url();
|
||||
return url === session.url('/#/home');
|
||||
});
|
||||
assert(foundHomeUrl);
|
||||
session.log.endGroup();
|
||||
}
|
35
test/end-to-end-tests/src/usecases/select-room.ts
Normal file
35
test/end-to-end-tests/src/usecases/select-room.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { findSublist } from "./create-room";
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function selectRoom(session: ElementSession, name: string): Promise<void> {
|
||||
session.log.step(`select "${name}" room`);
|
||||
const inviteSublist = await findSublist(session, "rooms");
|
||||
const invitesHandles = await inviteSublist.$$(".mx_RoomTile_name");
|
||||
const invitesWithText = await Promise.all(invitesHandles.map(async (roomHandle) => {
|
||||
const text = await session.innerText(roomHandle);
|
||||
return { roomHandle, text };
|
||||
}));
|
||||
const roomHandle = invitesWithText.find(({ roomHandle, text }) => {
|
||||
return text.trim() === name;
|
||||
}).roomHandle;
|
||||
|
||||
await roomHandle.click();
|
||||
|
||||
session.log.done();
|
||||
}
|
85
test/end-to-end-tests/src/usecases/send-sticker.ts
Normal file
85
test/end-to-end-tests/src/usecases/send-sticker.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Frame } from "puppeteer";
|
||||
|
||||
import { ElementSession } from "../session";
|
||||
|
||||
export async function sendSticker(session: ElementSession): Promise<void> {
|
||||
session.log.step(`opens composer menu`);
|
||||
const kebabButton = await session.query('.mx_MessageComposer_buttonMenu');
|
||||
await kebabButton.click();
|
||||
session.log.done();
|
||||
|
||||
let stickerFrame: Frame;
|
||||
|
||||
// look to see if the sticker picker is already there (it's persistent, so
|
||||
// it will only load a new frame the first time we open it)
|
||||
for (const f of session.page.frames()) {
|
||||
if ((await f.title()) === "Fake Sticker Picker") {
|
||||
stickerFrame = f;
|
||||
}
|
||||
}
|
||||
|
||||
const stickerFramePromise = new Promise<Frame>(resolve => {
|
||||
session.page.once('frameattached', async f => {
|
||||
await f.waitForNavigation();
|
||||
resolve(f);
|
||||
});
|
||||
});
|
||||
|
||||
session.log.step(`opens sticker picker`);
|
||||
|
||||
const stickerOption = await session.query('#stickersButton');
|
||||
await stickerOption.click();
|
||||
|
||||
if (stickerFrame === undefined) {
|
||||
stickerFrame = await stickerFramePromise;
|
||||
}
|
||||
|
||||
if (stickerFrame === undefined) throw new Error("Couldn't find sticker picker frame");
|
||||
session.log.done();
|
||||
|
||||
session.log.step(`clicks sticker button`);
|
||||
|
||||
const sendStickerButton = await stickerFrame.waitForSelector('#sendsticker');
|
||||
sendStickerButton.click();
|
||||
|
||||
// wait for the message to appear sent
|
||||
await session.query(".mx_EventTile_last:not(.mx_EventTile_sending)");
|
||||
|
||||
const stickerSrc = await session.page.evaluate(() => {
|
||||
return document.querySelector(
|
||||
'.mx_EventTile_last .mx_MStickerBody_wrapper img',
|
||||
).getAttribute('src');
|
||||
});
|
||||
|
||||
if (!stickerSrc.split('?')[0].endsWith('/_matrix/media/r0/thumbnail/somewhere')) {
|
||||
throw new Error("Unexpected image src for sticker: got " + stickerSrc);
|
||||
}
|
||||
|
||||
const stickerAlt = await session.page.evaluate(() => {
|
||||
return document.querySelector(
|
||||
'.mx_EventTile_last .mx_MStickerBody_wrapper img',
|
||||
).getAttribute('alt');
|
||||
});
|
||||
|
||||
if (stickerAlt !== "Test Sticker") {
|
||||
throw new Error("Unexpected image alt for sticker: got " + stickerAlt);
|
||||
}
|
||||
|
||||
session.log.done();
|
||||
}
|
Loading…
Reference in a new issue