Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2E - Auth Dashboard #419

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/auth-dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Auth Dashboard - E2ETests

on:
workflow_dispatch:
push:
branches:
- master
schedule:
- cron: "*/30 * * * *"

env:
NODE_VERSION: 20

jobs:
build:
name: Auth Dashboard - E2ETests
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Trigger tests
env:
CI: true
CI_MODE: ${{ secrets.CI_MODE }}
SMS_MOBILE_NUMBER: ${{ secrets.SMS_MOBILE_NUMBER }}
LOGIN_MOBILE_NUMBER: ${{ secrets.LOGIN_MOBILE_NUMBER }}
BACKUP_PHRASE_PROD: ${{ secrets.BACKUP_PHRASE_PROD }}
BACKUP_PHRASE_CYAN: ${{ secrets.BACKUP_PHRASE_CYAN }}
BACKUP_PHRASE_AQUA: ${{ secrets.BACKUP_PHRASE_AQUA }}
TESTMAIL_APP_APIKEY: ${{ secrets.TESTMAIL_APP_APIKEY }}
MAIL_APP: ${{ secrets.MAIL_APP }}
run: |
ifconfig && npm install && npx playwright install && npm run test:authdashboard

- name: Get current timestamp
id: get-time
run: echo "::set-output name=timestamp::$(date +%Y%m%d%H%M%S)"

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: artifact-${{ github.run_id }}-${{ github.job }}-${{ steps.get-time.outputs.timestamp }}
path: test-results/*
if-no-files-found: ignore
- name: Update Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
title: ${{ github.workflow}} - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
status: ${{ job.status }}
nocontext: true
201 changes: 201 additions & 0 deletions authservice/auth-dashboard/AuthDashboardPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// playwright-dev-page.ts
import { Browser, expect, Page } from "@playwright/test";

import { delay, getRecoveryPhase, signInByPhoneWithSMSOtp } from "../utils";
import { Locale } from "./locale";

const phoneNumberFormatInput = "+358-4573992100";
const phoneNumberFormatUrlParam = "3584573992100";

function validateDate(dateTime: string) {
const regex = /^([0-2]\d|3[01])\/(0\d|1[0-2])\/\d{2} ([0-1]\d|2[0-3]):[0-5]\d$/;
return regex.test(dateTime);
}

export class AuthDashboardPage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async verifyStrongSecurity() {
const imageDisplay = await this.page.locator(`img[alt="Strong Security"]`).isVisible();
const titleAlertDisplay = await this.page.locator(`text="Strong Security"`).isVisible();
const descriptionAlertDisplay = await this.page
.locator(`text="Your account is secured with at least three recovery factors. You can still add more for added protection."`)
.isVisible();

return imageDisplay && titleAlertDisplay && descriptionAlertDisplay;
}

async verifyAuthenticatorSetup(email: string) {
return this.page.locator(`//div[*/div[text()='Authenticator App']]//div[text()='Web3Auth-${email}']`).isVisible();
}

async verifyAuthenticatorCannotBeDeleted() {
return this.page.locator(`//div[*/div[text()='Authenticator App']]//button[type="button"]`).isVisible({ timeout: 2000 });
}

async verifyEmailPasswordlessSetup(email: string) {
return this.page.locator(`//div[text()='Email account']/following-sibling::div[text()='${email}']`).first().isVisible();
}

async verifyDarkMode() {
expect(await this.page.locator(`html.dark`).first().isVisible()).toBeTruthy();
}

async verifyLightMode() {
expect(await this.page.locator(`html.dark`).first().isVisible()).toBeFalsy();
}

async verifyMultipleLanguages() {
for (const key of Object.keys(Locale)) {
await this.changeLanguage(Locale[key]["langName"]);
await delay(1000);
await this.verifySingleLanguage(key);
}
}

async verifySingleLanguage(lang: string) {
const objLang = Locale[lang];
for (const key of Object.keys(objLang)) {
console.log(`Verify language ${lang} key ${key} with value ${objLang[key]}`);
await expect(this.page.locator(`text="${objLang[key]}"`).first()).toBeVisible();
}
}

async verifySMSSocialFactorSetup() {
return this.page
.locator(`//div[div[text()='Social Recovery']]/following-sibling::div//div[text()='${phoneNumberFormatInput}']`)
.first()
.isVisible();
}

async verifyDeviceSetup(browserType: string) {
const browserRecord = await this.page.locator(`//div[*/div[text()='Devices']]//div[contains(text(),'${browserType}')]`).isVisible();
const currentTag = await this.page.locator(`//div[*/div[text()='Devices']]//div[contains(text(),'Current')]`).isVisible();

const timeFortmat = validateDate(
(await this.page.locator(`//div[*/div[text()='Devices']]//span[contains(text(),'Created: ')]`).textContent()).replace("Created: ", "")
);
return browserRecord && currentTag && timeFortmat;
}

async changeDarkLightMode() {
await this.page.locator(`button.icon[icon]`).first().click();
await delay(1000);
}

async addPasswordFactor() {
await this.page.click(`text=" Setup Password"`);
await this.page.fill(`input[aria-placeholder="Set your password"]`, "Testing@123");
await this.page.fill(`input[aria-placeholder="Re-enter your password"]`, "Testing@123");
await this.page.click(`button[aria-label="Confirm"][type="submit"]`);
}

async addSMSSocialFactor(browser: Browser) {
await this.page.click(`text=" Setup Social Recovery"`);
await this.page.fill(`input#passwordless-email`, phoneNumberFormatInput);
await this.page.click(`button[aria-label="Connect with Phone"][type="submit"]`);

await signInByPhoneWithSMSOtp(phoneNumberFormatUrlParam, browser);
}

async addRecoverPhrase(emailRecovery: string, tag: string) {
await this.page.click(`text=" Generate recovery phrase"`);
await this.page.fill(`input[aria-placeholder="name@example.com"]`, emailRecovery);
await this.page.click(`button[data-testid="send-recovery-factor"]`);

const recoveryPhrase = await getRecoveryPhase({
email: emailRecovery,
tag,
timestamp: Math.floor(Date.now() / 1000),
});

await this.page.fill(`textarea[placeholder="Paste your recovery phrase"]`, recoveryPhrase);
await this.page.click(`button[data-testid="verify"]`);

await this.page.locator(`text="Recovery phrase sent to Email"`).waitFor({ state: "visible" });

return recoveryPhrase;
}

async verifyPasswordSetup() {
return !this.page.locator(`text=" Setup Password"`).isVisible() && this.page.locator(`text="Change Password"`).isVisible();
}

async verifyPasswordNotSetupYet() {
return this.page.locator(`text=" Setup Password"`).isVisible();
}

async changeLanguage(lang: string) {
await this.page.click("[data-dropdown-toggle='dropdown']");
await this.page.click(`text="${lang}"`);
}

async changePasswordSetup() {
await this.page.click(`text="Change Password"`);
await this.page.fill(`input[aria-placeholder="Set your password"]`, "Testing@123");
await this.page.fill(`input[aria-placeholder="Re-enter your password"]`, "Testing@123");
await this.page.click(`button[aria-label="Confirm"][type="submit"]`);
}

async deletePasswordSetup() {
await this.page.click(`button[aria-label="Delete Password"]`);

await this.page.locator(`text="Remove Password"`).waitFor({ state: "visible" });

const lisEle = await this.page.$$(`button[aria-label="Confirm"]`);
for (const element of lisEle) {
if (await element.isVisible()) {
await element.click();
break;
}
}

await this.page.locator(`text=" Setup Password"`).waitFor({ state: "visible" });
}

async deleteRecoveryPhrase() {
await this.page.click(`button[aria-label="Delete Recovery Share"]`);

await this.page.locator(`text="Delete Recovery Phrase"`).waitFor({ state: "visible" });

const lisEle = await this.page.$$(`button[aria-label="Confirm"]`);
for (const element of lisEle) {
if (await element.isVisible()) {
await element.click();
break;
}
}

await this.page.locator(`text=" Generate recovery phrase"`).waitFor({ state: "visible" });
}

async deleteSocialFactor() {
await this.page.click(`button[aria-label="Delete Social Share"]`);

await this.page.locator(`text="Deleting Social Recovery Factor"`).first().waitFor({ state: "visible" });

const lisEle = await this.page.$$(`button[aria-label="Confirm"]`);
for (const element of lisEle) {
if (await element.isVisible()) {
await element.click();
break;
}
}

await this.page.locator(`text=" Setup Social Recovery"`).waitFor({ state: "visible" });
}

async verifyRecoverPhraseSetup(phrase: string, bkEmail: string) {
const content = await this.page.locator(`//div[text()='Recovery phrase']/parent::div/parent::div`).first().textContent();

const containPhrase = content.includes(phrase);
const containBkEmail = content.includes(bkEmail);
const dateTimeFormat = validateDate(content.split("Generated on: ")[1]);

return containBkEmail && containPhrase && dateTimeFormat;
}
}
31 changes: 31 additions & 0 deletions authservice/auth-dashboard/LoginAuthDashboardPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// playwright-dev-page.ts
import { Page } from "@playwright/test";

export class LoginAuthDashboardPage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async gotoLoginAuthDashboardPage() {
await this.page.goto("https://develop-account.web3auth.io/");
}

async clickLoginButton() {
await this.page.click(`text="Connect with Phone or Email"`);
}

async inputEmailPasswordless(email: string) {
await this.page.fill(`input[aria-labelledby="Phone or Email"]`, email);
}

async verifyLogout() {
await this.page.locator(`input[aria-labelledby="Phone or Email"]`).waitFor({ state: "visible", timeout: 5000 });
}

async logout() {
await this.page.click(`button[aria-label="Logout"]`);
await this.page.locator(`input[aria-labelledby="Phone or Email"]`).waitFor({ state: "visible" });
}
}
80 changes: 80 additions & 0 deletions authservice/auth-dashboard/auth-dashboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect, test } from "@playwright/test";

import { AuthServicePage } from "../login-with-passwordless/AuthServicePage";
import { delay, generateEmailWithTag, verifyEmailPasswordlessWithVerificationCode } from "../utils";
import { AuthDashboardPage } from "./AuthDashboardPage";
import { LoginAuthDashboardPage } from "./LoginAuthDashboardPage";

test.describe("Passwordless Login scenarios", () => {
test.setTimeout(90000);

test("Login and set up Auth Dashboard, @authdashboard", async ({ page, browser }) => {
const testEmail = generateEmailWithTag();
const testBackupEmail = generateEmailWithTag();
const loginPage = new LoginAuthDashboardPage(page);

// LOGIN TO THE DASHBOARD

await loginPage.gotoLoginAuthDashboardPage();
await loginPage.inputEmailPasswordless(testEmail);
await loginPage.clickLoginButton();

const tag = testEmail.split("@")[0].split(".")[1];
const tagBk = testBackupEmail.split("@")[0].split(".")[1];

await verifyEmailPasswordlessWithVerificationCode(page, browser, {
email: testEmail,
tag,
timestamp: Math.floor(Date.now() / 1000),
redirectMode: false,
previousCode: "",
});

await delay(2000);
const pages = browser.contexts()[0].pages();

const authServicePage = new AuthServicePage(pages[1]);
await pages[1].bringToFront();

await authServicePage.clickSetup2FA();
await authServicePage.setupAuthenticatorNewMFAFlow();
await authServicePage.finishSetupNewMFAList();
await authServicePage.setupPasskeyLater();
await authServicePage.confirmDone2FASetup();

const authDashboardPage = new AuthDashboardPage(page);
await delay(5000);
expect(await authDashboardPage.verifyEmailPasswordlessSetup(testEmail)).toBeTruthy();
expect(await authDashboardPage.verifyAuthenticatorSetup(testEmail)).toBeTruthy();
expect(await authDashboardPage.verifyAuthenticatorCannotBeDeleted()).toBeFalsy();
expect(await authDashboardPage.verifyDeviceSetup("Chrome")).toBeTruthy();

await authDashboardPage.addPasswordFactor();
await authDashboardPage.verifyPasswordSetup();

await authDashboardPage.changePasswordSetup();
await authDashboardPage.verifyPasswordSetup();

await authDashboardPage.deletePasswordSetup();
expect(await authDashboardPage.verifyPasswordNotSetupYet()).toBeTruthy();

const phrase = await authDashboardPage.addRecoverPhrase(testBackupEmail, tagBk);
await authDashboardPage.verifyRecoverPhraseSetup(phrase, testBackupEmail);
await authDashboardPage.deleteRecoveryPhrase();

await authDashboardPage.addSMSSocialFactor(browser);
await authDashboardPage.verifySMSSocialFactorSetup();
await authDashboardPage.deleteSocialFactor();

await authDashboardPage.verifyMultipleLanguages();

await authDashboardPage.changeDarkLightMode();
await authDashboardPage.verifyDarkMode();
await authDashboardPage.changeDarkLightMode();
await authDashboardPage.verifyLightMode();

await authDashboardPage.changeLanguage("English");
await loginPage.logout();
await loginPage.verifyLogout();
});
});
Loading
Loading