chore: fixes to tests

This commit is contained in:
Stefan Benz
2024-11-20 14:22:22 +01:00
parent 64beeabce4
commit f06b22f18a
20 changed files with 404 additions and 103 deletions

View File

@@ -0,0 +1,12 @@
import { expect, Page } from "@playwright/test";
const codeTextInput = "code-text-input";
export async function codeScreen(page: Page, code: string) {
await page.getByTestId(codeTextInput).pressSequentially(code);
}
export async function codeScreenExpect(page: Page, code: string) {
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code");
}

19
acceptance/tests/code.ts Normal file
View File

@@ -0,0 +1,19 @@
import { Page } from "@playwright/test";
import { codeScreen } from "./code-screen";
import { getCodeFromSink } from "./sink";
export async function codeFromSink(page: Page, key: string) {
// wait for send of the code
await page.waitForTimeout(2000);
const c = await getCodeFromSink(key);
await code(page, c);
}
export async function code(page: Page, code: string) {
await codeScreen(page, code);
await page.getByTestId("submit-button").click();
}
export async function codeResend(page: Page) {
await page.getByTestId("resend-button").click();
}

View File

@@ -1,4 +1,5 @@
import { expect, Page } from "@playwright/test";
import { codeFromSink } from "./code";
import { loginname } from "./loginname";
import { password } from "./password";
@@ -23,6 +24,12 @@ export async function loginScreenExpect(page: Page, fullName: string) {
await expect(page.getByRole("heading")).toContainText(fullName);
}
export async function loginWithOTP(page: Page, username: string, password: string) {
export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) {
await loginWithPassword(page, username, password);
await codeFromSink(page, email);
}
export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) {
await loginWithPassword(page, username, password);
await codeFromSink(page, phone);
}

View File

@@ -1,12 +1,12 @@
import { expect, Page } from "@playwright/test";
const usernameUserInput = "username-text-input";
const usernameTextInput = "username-text-input";
export async function loginnameScreen(page: Page, username: string) {
await page.getByTestId(usernameUserInput).pressSequentially(username);
await page.getByTestId(usernameTextInput).pressSequentially(username);
}
export async function loginnameScreenExpect(page: Page, username: string) {
await expect(page.getByTestId(usernameUserInput)).toHaveValue(username);
await expect(page.getByTestId(usernameTextInput)).toHaveValue(username);
await expect(page.getByTestId("error").locator("div")).toContainText("Could not find user");
}

View File

@@ -1,31 +0,0 @@
import * as http from "node:http";
let messages = new Map<string, any>();
export function startSink() {
const hostname = "127.0.0.1";
const port = 3030;
const server = http.createServer((req, res) => {
console.log("Sink received message: ");
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
console.log(body);
const data = JSON.parse(body);
messages.set(data.contextInfo.recipientEmailAddress, data.args.code);
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.write("OK");
res.end();
});
});
server.listen(port, hostname, () => {
console.log(`Sink running at http://${hostname}:${port}/`);
});
return server;
}

View File

@@ -1,13 +1,19 @@
import { faker } from "@faker-js/faker";
import { test } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { loginScreenExpect } from "./login";
import { registerWithPasskey, registerWithPassword } from "./register";
import { removeUserByUsername } from "./zitadel";
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
test("register with password", async ({ page }) => {
const username = "register-password@example.com";
const username = faker.internet.email();
const password = "Password1!";
const firstname = "firstname";
const lastname = "lastname";
const firstname = faker.person.firstName();
const lastname = faker.person.lastName();
await removeUserByUsername(username);
await registerWithPassword(page, firstname, lastname, username, password, password);
@@ -15,9 +21,9 @@ test("register with password", async ({ page }) => {
});
test("register with passkey", async ({ page }) => {
const username = "register-passkey@example.com";
const firstname = "firstname";
const lastname = "lastname";
const username = faker.internet.email();
const firstname = faker.person.firstName();
const lastname = faker.person.lastName();
await removeUserByUsername(username);
await registerWithPasskey(page, firstname, lastname, username);

28
acceptance/tests/sink.ts Normal file
View File

@@ -0,0 +1,28 @@
import axios from "axios";
export async function getCodeFromSink(key: string): Promise<any> {
try {
const response = await axios.post(
process.env.SINK_NOTIFICATION_URL,
{
recipient: key,
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`,
},
},
);
if (response.status >= 400) {
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
console.error(error);
throw new Error(error);
}
return response.data.args.oTP;
} catch (error) {
console.error("Error making request:", error);
throw error;
}
}

View File

@@ -9,6 +9,7 @@ export interface userProps {
lastName: string;
organization: string;
password: string;
phone: string;
}
class User {
@@ -35,6 +36,10 @@ class User {
email: this.props.email,
isVerified: true,
},
phone: {
phone: this.props.phone!,
isVerified: true,
},
password: {
password: this.props.password!,
},
@@ -53,6 +58,7 @@ class User {
console.error(error);
throw new Error(error);
}
this.setUserId(response.data.userId);
} catch (error) {
console.error("Error making request:", error);
throw error;
@@ -94,6 +100,10 @@ class User {
return this.props.lastName;
}
public getPhone() {
return this.props.phone;
}
public getFullName() {
return `${this.props.firstName} ${this.props.lastName}`;
}
@@ -112,12 +122,12 @@ export interface otpUserProps {
lastName: string;
organization: string;
password: string;
phone: string;
type: OtpType;
}
export class PasswordUserWithOTP extends User {
private type: OtpType;
private code: string;
constructor(props: otpUserProps) {
super({
@@ -126,6 +136,7 @@ export class PasswordUserWithOTP extends User {
lastName: props.lastName,
organization: props.organization,
password: props.password,
phone: props.phone,
});
this.type = props.type;
}
@@ -160,9 +171,6 @@ export class PasswordUserWithOTP extends User {
console.error(error);
throw new Error(error);
}
// TODO: get code from SMS or Email provider
this.code = "";
} catch (error) {
console.error("Error making request:", error);
throw error;
@@ -171,10 +179,6 @@ export class PasswordUserWithOTP extends User {
// wait for projection of user
await page.waitForTimeout(2000);
}
public getCode() {
return this.code;
}
}
export interface passkeyUserProps {
@@ -182,6 +186,7 @@ export interface passkeyUserProps {
firstName: string;
lastName: string;
organization: string;
phone: string;
}
export class PasskeyUser extends User {
@@ -194,6 +199,7 @@ export class PasskeyUser extends User {
lastName: props.lastName,
organization: props.organization,
password: "",
phone: props.phone,
});
}

View File

@@ -1,3 +1,4 @@
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
@@ -10,10 +11,11 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasskeyUser }>({
user: async ({ page }, use) => {
const user = new PasskeyUser({
email: "passkey@example.com",
firstName: "first",
lastName: "last",
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
});
await user.ensure(page);
await use(user);
@@ -25,7 +27,7 @@ test("username and passkey login", async ({ user, page }) => {
await loginScreenExpect(page, user.getFullName());
});
test("username and passkey login, if passkey enabled", async ({ user, page }) => {
test("username and passkey login, if passkey enabled", async ({ page }) => {
// Given passkey is enabled on the organization of the user
// Given the user has only passkey enabled as authentication
// enter username
@@ -34,7 +36,7 @@ test("username and passkey login, if passkey enabled", async ({ user, page }) =>
// user is redirected to app
});
test("username and passkey login, multiple auth methods", async ({ user, page }) => {
test("username and passkey login, multiple auth methods", async ({ page }) => {
// Given passkey and password is enabled on the organization of the user
// Given the user has password and passkey registered
// enter username

View File

@@ -1,3 +1,4 @@
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
@@ -12,11 +13,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasswordUser }>({
user: async ({ page }, use) => {
const user = new PasswordUser({
email: "password-changed@example.com",
firstName: "first",
lastName: "last",
password: "Password1!",
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
password: "Password1!",
});
await user.ensure(page);
await use(user);

View File

@@ -1,6 +1,33 @@
import { test } from "@playwright/test";
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { code, codeFromSink, codeResend } from "./code";
import { codeScreenExpect } from "./code-screen";
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login";
import { OtpType, PasswordUserWithOTP } from "./user";
test("username, password and email otp login, enter code manually", async ({ page }) => {
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
user: async ({ page }, use) => {
const user = new PasswordUserWithOTP({
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
password: "Password1!",
type: OtpType.email,
});
await user.ensure(page);
await use(user);
},
});
test("username, password and email otp login, enter code manually", async ({ user, page }) => {
// Given email otp is enabled on the organization of the user
// Given the user has only email otp configured as second factor
// User enters username
@@ -8,6 +35,8 @@ test("username, password and email otp login, enter code manually", async ({ pag
// User receives an email with a verification code
// User enters the code into the ui
// User is redirected to the app (default redirect url)
await loginWithPasswordAndEmailOTP(page, user.getUsername(), user.getPassword(), user.getUsername());
await loginScreenExpect(page, user.getFullName());
});
test("username, password and email otp login, click link in email", async ({ page }) => {
@@ -20,7 +49,7 @@ test("username, password and email otp login, click link in email", async ({ pag
// User is redirected to the app (default redirect url)
});
test("username, password and email otp login, resend code", async ({ page }) => {
test("username, password and email otp login, resend code", async ({ user, page }) => {
// Given email otp is enabled on the organization of the user
// Given the user has only email otp configured as second factor
// User enters username
@@ -30,16 +59,24 @@ test("username, password and email otp login, resend code", async ({ page }) =>
// User receives a new email with a verification code
// User enters the new code in the ui
// User is redirected to the app (default redirect url)
await loginWithPassword(page, user.getUsername(), user.getPassword());
await codeResend(page);
await codeFromSink(page, user.getUsername());
await loginScreenExpect(page, user.getFullName());
});
test("username, password and email otp login, wrong code", async ({ page }) => {
test("username, password and email otp login, wrong code", async ({ user, page }) => {
// Given email otp is enabled on the organization of the user
// Given the user has only email otp configured as second factor
// User enters username
// User enters password
// User receives an email with a verification code
// User enters a wrond code
// User enters a wrong code
// Error message - "Invalid code" is shown
const c = "wrongcode";
await loginWithPassword(page, user.getUsername(), user.getPassword());
await code(page, c);
await codeScreenExpect(page, c);
});
test("username, password and email otp login, multiple mfa options", async ({ page }) => {
@@ -49,7 +86,7 @@ test("username, password and email otp login, multiple mfa options", async ({ pa
// User enters password
// User receives an email with a verification code
// User clicks button to use sms otp as second factor
// User receives an sms with a verification code
// User receives a sms with a verification code
// User enters code in ui
// User is redirected to the app (default redirect url)
});

View File

@@ -1,6 +1,30 @@
import { test } from "@playwright/test";
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { OtpType, PasswordUserWithOTP } from "./user";
test("username, password and sms otp login", async ({ page }) => {
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
user: async ({ page }, use) => {
const user = new PasswordUserWithOTP({
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
password: "Password1!",
type: OtpType.sms,
});
await user.ensure(page);
await use(user);
},
});
test("username, password and sms otp login, enter code manually", async ({ user, page }) => {
// Given sms otp is enabled on the organization of the user
// Given the user has only sms otp configured as second factor
// User enters username
@@ -8,9 +32,13 @@ test("username, password and sms otp login", async ({ page }) => {
// User receives a sms with a verification code
// User enters the code into the ui
// User is redirected to the app (default redirect url)
/* TODO fix on login, that sms is sent
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
await loginScreenExpect(page, user.getFullName());
*/
});
test("username, password and sms otp login, resend code", async ({ page }) => {
test("username, password and sms otp login, resend code", async ({ user, page }) => {
// Given sms otp is enabled on the organization of the user
// Given the user has only sms otp configured as second factor
// User enters username
@@ -19,9 +47,14 @@ test("username, password and sms otp login, resend code", async ({ page }) => {
// User clicks resend code
// User receives a new sms with a verification code
// User is redirected to the app (default redirect url)
/* TODO fix on login, that sms is sent
await loginWithPassword(page, user.getUsername(), user.getPassword());
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
await loginScreenExpect(page, user.getFullName());
*/
});
test("username, password and sms otp login, wrong code", async ({ page }) => {
test("username, password and sms otp login, wrong code", async ({ user, page }) => {
// Given sms otp is enabled on the organization of the user
// Given the user has only sms otp configured as second factor
// User enters username
@@ -29,4 +62,10 @@ test("username, password and sms otp login, wrong code", async ({ page }) => {
// User receives a sms with a verification code
// User enters a wrong code
// Error message - "Invalid code" is shown
/* TODO fix on login, that sms is sent
const c = "wrongcode";
await loginWithPassword(page, user.getUsername(), user.getPassword());
await code(page, c);
await codeScreenExpect(page, c);
*/
});

View File

@@ -1,3 +1,4 @@
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
@@ -14,11 +15,12 @@ dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasswordUser }>({
user: async ({ page }, use) => {
const user = new PasswordUser({
email: "password@example.com",
firstName: "first",
lastName: "last",
password: "Password1!",
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
password: "Password1!",
});
await user.ensure(page);
await use(user);