diff --git a/playwright.config.ts b/playwright.config.ts index 89d5761d..90cc79a2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,8 @@ import { defineConfig, devices } from "@playwright/test"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./playwright", + testDir: "./playwright/specs", + snapshotPathTemplate: "./playwright/snapshots/{testFilePath}/{arg}{ext}", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/playwright/PageObjects/LoginPin.ts b/playwright/PageObjects/LoginPin.ts index 1b98d513..f336119d 100644 --- a/playwright/PageObjects/LoginPin.ts +++ b/playwright/PageObjects/LoginPin.ts @@ -84,9 +84,12 @@ export class LoginPinPage extends MainPage { } async enterPin(pin: string) { - for (const digit of pin.split("")) { - await this.page.locator(`[data-cy='button-pin-${digit}']`).click(); - } + await this.page.keyboard.type(pin, { delay: 100 }); + } + + async enterDefaultPin() { + await this.page.keyboard.type("123456", { delay: 100 }); + await this.pinButtonConfirm.click(); } async goToPinSettings() { diff --git a/playwright/PageObjects/MainPage.ts b/playwright/PageObjects/MainPage.ts index b32857cd..b25d724f 100644 --- a/playwright/PageObjects/MainPage.ts +++ b/playwright/PageObjects/MainPage.ts @@ -41,6 +41,34 @@ export default class MainPage { this.toastNotificationText = page.getByTestId("toast-notification-text"); } + async assertInputTextSelected(selector: string) { + // Locate the input field + const inputField = this.page.locator(selector); + + // Get the value of the input field + await inputField.click(); + const inputValue = await inputField.inputValue(); + + // Explicitly select the text in the input field + await this.page.evaluate((selector) => { + const input = document.querySelector(selector) as HTMLInputElement; + input.select(); + }, selector); + + // Evaluate the selection start and end + const selectionRange = await this.page.evaluate((selector) => { + const input = document.querySelector(selector) as HTMLInputElement; + return { + selectionStart: input.selectionStart, + selectionEnd: input.selectionEnd, + }; + }, selector); + + // Assert that the whole text is selected + expect(selectionRange.selectionStart).toBe(0); + expect(selectionRange.selectionEnd).toBe(inputValue.length); + } + async ensureSidebarIsDisplayed() { const hasVerticalClass: boolean = await this.navigationBar.evaluate((el) => el.classList.contains("vertical"), @@ -93,4 +121,39 @@ export default class MainPage { return await navigator.clipboard.readText(); }); } + + async validatePseudoElementContent( + selector: string, + expectedContent: string, + ) { + // Hover over the element to trigger the pseudo-element + await this.page.hover(selector); + + // Evaluate the content of the ::after pseudo-element + const content = await this.page.evaluate((selector) => { + const element = document.querySelector(selector); + if (element) { + const style = window.getComputedStyle(element, "::after"); + return style.content; + } + return null; + }, selector); + + // Validate the content + expect(content).toBe(`"${expectedContent}"`); + } + + async validateTooltipAttribute( + selector: string, + expectedTooltipText: string, + ) { + // Locate the element that should have the data-tooltip attribute + const element = this.page.locator(selector); + + // Get the value of the data-tooltip attribute + const tooltipText = await element.getAttribute("data-tooltip"); + + // Validate that the data-tooltip attribute has the expected value + expect(tooltipText).toBe(expectedTooltipText); + } } diff --git a/playwright/PageObjects/Settings/SettingsProfile.ts b/playwright/PageObjects/Settings/SettingsProfile.ts index 4a98c78d..0c01959a 100644 --- a/playwright/PageObjects/Settings/SettingsProfile.ts +++ b/playwright/PageObjects/Settings/SettingsProfile.ts @@ -1,8 +1,11 @@ -import { type Locator, type Page } from "@playwright/test"; +import { expect, type Locator, type Page } from "@playwright/test"; import { SettingsBase } from "./SettingsBase"; export class SettingsProfile extends SettingsBase { readonly page: Page; + readonly contextMenuUserID: Locator; + readonly contextMenuOptionCopyDID: Locator; + readonly contextMenuOptionCopyID: Locator; readonly inputSettingsProfileShortID: Locator; readonly inputSettingsProfileShortIDGroup: Locator; readonly inputSettingsProfileStatus: Locator; @@ -14,9 +17,11 @@ export class SettingsProfile extends SettingsBase { readonly logOutSectionLabel: Locator; readonly logOutSectionText: Locator; readonly profileBanner: Locator; + readonly profileBannerContainer: Locator; readonly profileBannerInput: Locator; readonly profileImageFrame: Locator; readonly profilePicture: Locator; + readonly profilePictureContainer: Locator; readonly profilePictureInput: Locator; readonly profilePictureUploadButton: Locator; readonly profilePictureImage: Locator; @@ -39,19 +44,27 @@ export class SettingsProfile extends SettingsBase { readonly onlineStatusSectionSelectorCurrentlyOnline: Locator; readonly onlineStatusSectionSelectOptions: Locator; readonly onlineStatusSectionText: Locator; - readonly startRecoverySeedSection: Locator; - readonly startRecoverySeedCheckbox: Locator; - readonly startRecoverySeedText: Locator; + readonly storeRecoverySeedSection: Locator; + readonly storeRecoverySeedCheckbox: Locator; + readonly storeRecoverySeedText: Locator; + readonly warningMessage: Locator; constructor(page: Page) { super(page); this.page = page; + this.contextMenuUserID = page.locator("#context-menu"); + this.contextMenuOptionCopyDID = page.getByTestId( + "context-menu-option-Copy DID", + ); + this.contextMenuOptionCopyID = page.getByTestId( + "context-menu-option-Copy ID", + ); this.inputSettingsProfileShortID = page.getByTestId( "input-settings-profile-short-id", ); - this.inputSettingsProfileShortIDGroup = this.inputSettingsProfileShortID - .locator("..") - .locator(".input-group"); + this.inputSettingsProfileShortIDGroup = page.locator( + '[data-tooltip="Copy"]', + ); this.inputSettingsProfileStatus = page.getByTestId( "input-settings-profile-status-message", ); @@ -65,91 +78,74 @@ export class SettingsProfile extends SettingsBase { "label-settings-profile-username", ); this.logOutSection = page.getByTestId("section-log-out"); - this.logOutSectionButton = this.logOutSection.locator( - '[data-cy="button-log-out"]', + this.logOutSectionButton = this.logOutSection.getByTestId("button-log-out"); + this.logOutSectionLabel = this.logOutSection.getByTestId( + "setting-section-label", ); - this.logOutSectionLabel = this.logOutSection.locator( - '[data-cy="setting-section-label"]', - ); - this.logOutSectionText = this.logOutSection.locator( - '[data-cy="setting-section-text"]', + this.logOutSectionText = this.logOutSection.getByTestId( + "setting-section-text", ); this.profileBanner = page.getByTestId("profile-banner"); - this.profileBannerInput = this.profilePictureUploadButton - .locator("..") - .locator("..") - .locator(".profile-picture-container") - .locator("input"); + this.profileBannerContainer = page.locator(".profile-header"); + this.profileBannerInput = this.profileBannerContainer.locator("input"); this.profileImageFrame = page.getByTestId("profile-image-frame"); this.profilePicture = page.getByTestId("profile-picture"); - this.profilePictureInput = page - .getByTestId("profile-picture-input") - .locator("..") - .locator("input"); - this.profilePictureUploadButton = page.getByTestId( - "profile-picture-upload", - ); + this.profilePictureContainer = page.locator(".profile-picture-container"); + this.profilePictureInput = this.profilePictureContainer.locator("input"); + this.profilePictureUploadButton = + this.profilePictureContainer.getByTestId("button-file-upload"); this.profilePictureImage = this.profilePicture.locator("img"); this.revealPhraseSection = page.getByTestId("section-reveal-phrase"); - this.revealPhraseSectionHideButton = this.revealPhraseSection.locator( - '[data-cy="button-hide-phrase"]', - ); - this.revealPhraseSectionRevealButton = this.revealPhraseSection.locator( - '[data-cy="button-reveal-phrase"]', - ); - this.revealPhraseSectionButtonCopyPhrase = this.revealPhraseSection.locator( - '[data-cy="button-copy-phrase"]', - ); - this.revealPhraseSectionLabel = this.revealPhraseSection.locator( - '[data-cy="setting-section-label"]', - ); - this.revealPhraseSectionText = this.revealPhraseSection.locator( - '[data-cy="setting-section-text"]', + this.revealPhraseSectionHideButton = + this.revealPhraseSection.getByTestId("button-hide-phrase"); + this.revealPhraseSectionRevealButton = this.revealPhraseSection.getByTestId( + "button-reveal-phrase", ); - this.revealPhraseSectionWordNumber = this.revealPhraseSection.locator( - '[data-cy="word-number"]', + this.revealPhraseSectionButtonCopyPhrase = + this.revealPhraseSection.getByTestId("button-copy-phrase"); + this.revealPhraseSectionLabel = this.revealPhraseSection.getByTestId( + "setting-section-label", ); - this.revealPhraseSectionWordValue = this.revealPhraseSection.locator( - '[data-cy="word-value"]', + this.revealPhraseSectionText = this.revealPhraseSection.getByTestId( + "setting-section-text", ); + this.revealPhraseSectionWordNumber = + this.revealPhraseSection.getByTestId("word-number"); + this.revealPhraseSectionWordValue = + this.revealPhraseSection.getByTestId("word-value"); this.saveControls = page.getByTestId("save-controls"); - this.saveControlsButtonCancel = this.saveControls.locator( - '[data-cy="button-cancel"]', - ); - this.saveControlsButtonSave = this.saveControls.locator( - '[data-cy="button-save"]', - ); + this.saveControlsButtonCancel = + this.saveControls.getByTestId("button-cancel"); + this.saveControlsButtonSave = this.saveControls.getByTestId("button-save"); this.onlineStatusSection = page.getByTestId("section-online-status"); - this.onlineStatusSectionLabel = this.onlineStatusSection.locator( - '[data-cy="setting-section-label"]', + this.onlineStatusSectionLabel = this.onlineStatusSection.getByTestId( + "setting-section-label", ); this.onlineStatusSectionSelectorCurrentlyDoNotDisturb = - this.onlineStatusSection.locator( - '[data-cy="selector-currently-do-not-disturb"]', + this.onlineStatusSection.getByTestId( + "selector-current-status-do-not-disturb", ); this.onlineStatusSectionSelectorCurrentlyIdle = - this.onlineStatusSection.locator('[data-cy="selector-currently-idle"]'); + this.onlineStatusSection.getByTestId("selector-current-status-idle"); this.onlineStatusSectionSelectorCurrentlyOffline = - this.onlineStatusSection.locator( - '[data-cy="selector-currently-offline"]', - ); + this.onlineStatusSection.getByTestId("selector-current-status-offline"); this.onlineStatusSectionSelectorCurrentlyOnline = - this.onlineStatusSection.locator('[data-cy="selector-currently-online"]'); - this.onlineStatusSectionSelectOptions = this.onlineStatusSection.locator( - '[data-cy="select-options"]', + this.onlineStatusSection.getByTestId("selector-current-status-online"); + this.onlineStatusSectionSelectOptions = + this.onlineStatusSection.getByTestId("select-options"); + this.onlineStatusSectionText = this.onlineStatusSection.getByTestId( + "setting-section-text", ); - this.onlineStatusSectionText = this.onlineStatusSection.locator( - '[data-cy="setting-section-text"]', + this.storeRecoverySeedSection = page.getByTestId( + "section-store-recovery-seed", ); - this.startRecoverySeedSection = page.getByTestId( - "section-start-recovery-seed", + this.storeRecoverySeedCheckbox = this.storeRecoverySeedSection.getByTestId( + "checkbox-store-recovery-seed", ); - this.startRecoverySeedCheckbox = this.startRecoverySeedSection.locator( - '[data-cy="checkbox-start-recovery-seed"]', - ); - this.startRecoverySeedText = this.startRecoverySeedSection.locator( - '[data-cy="setting-section-text"]', + this.storeRecoverySeedText = this.storeRecoverySeedSection.getByTestId( + "text-store-recovery-seed", ); + this.warningMessage = page.locator(".warning"); } // Rewrite everything here in playwright @@ -171,9 +167,7 @@ export class SettingsProfile extends SettingsBase { // Iterate through each element and extract the value for (let i = 0; i < count; i++) { const element = elements.nth(i); - const value = await element.getAttribute("value"); // Assuming the value you want is an attribute - console.log("Option: ", await element.innerText()); - console.log("Option Val: ", value); + const value = await element.innerText(); // Assuming the value you want is an attribute options.push(value); } @@ -190,17 +184,17 @@ export class SettingsProfile extends SettingsBase { for (let i = 1; i <= 12; i++) { // Ensure the phrase number element exists await this.page - .locator(`[data-test="ordered-phrase-number-${i}"]`) + .locator(`[data-cy="ordered-phrase-number-${i}"]`) .waitFor({ state: "visible" }); // Ensure the phrase word element exists await this.page - .locator(`[data-test="ordered-phrase-word-${i}"]`) + .locator(`[data-cy="ordered-phrase-word-${i}"]`) .waitFor({ state: "visible" }); // Get the text from the

tag inside the phrase word element const text = await this.page - .locator(`[data-test="ordered-phrase-word-${i}"]`) + .locator(`[data-cy="ordered-phrase-word-${i}"]`) .locator("p") .innerText(); phrase.push(text); @@ -209,14 +203,46 @@ export class SettingsProfile extends SettingsBase { return phrase; } + async openUserIDContextMenu() { + await this.inputSettingsProfileShortIDGroup.click({ button: "right" }); + await this.contextMenuUserID.waitFor({ state: "visible" }); + } + + async selectOnlineStatus( + option: "online" | "idle" | "do-not-disturb" | "offline", + ) { + switch (option) { + case "online": + await this.onlineStatusSection + .locator("select") + .selectOption({ label: "Online" }); + break; + case "idle": + await this.onlineStatusSection + .locator("select") + .selectOption({ label: "Idle" }); + break; + case "do-not-disturb": + await this.onlineStatusSection + .locator("select") + .selectOption({ label: "Do Not Disturb" }); + break; + case "offline": + await this.onlineStatusSection + .locator("select") + .selectOption({ label: "Offline" }); + break; + } + } + async validateRecoveryPhraseIsHidden() { // Ensure the phrase number and word elements do not exist for (let i = 1; i <= 12; i++) { await this.page - .locator(`[data-test="ordered-phrase-number-${i}"]`) + .locator(`[data-cy="ordered-phrase-number-${i}"]`) .waitFor({ state: "hidden" }); await this.page - .locator(`[data-test="ordered-phrase-word-${i}"]`) + .locator(`[data-cy="ordered-phrase-word-${i}"]`) .waitFor({ state: "hidden" }); } } @@ -225,31 +251,33 @@ export class SettingsProfile extends SettingsBase { // Ensure the phrase number and word elements exist for (let i = 1; i <= 12; i++) { await this.page - .locator(`[data-test="ordered-phrase-number-${i}"]`) + .locator(`[data-cy="ordered-phrase-number-${i}"]`) .waitFor({ state: "visible" }); await this.page - .locator(`[data-test="ordered-phrase-word-${i}"]`) + .locator(`[data-cy="ordered-phrase-word-${i}"]`) .waitFor({ state: "visible" }); } } async uploadProfileBanner(file: string) { + await this.profileBanner.click(); await this.profileBannerInput.setInputFiles(file); } async uploadProfilePicture(file: string) { + await this.profilePictureUploadButton.click(); await this.profilePictureInput.setInputFiles(file); } - async validateProfileBannerURLIsValid() { - const style = await this.profileBanner.getAttribute("style"); - expect( - style.startsWith('background-image: url("data:image/jpeg;base64'), - ).eq(true); + async validateBannerDisplayed() { + await expect(this.page).toHaveScreenshot({ + maxDiffPixels: 400, + }); } - async validateProfilePictureURLIsValid() { - const style = await this.profilePictureImage.getAttribute("src"); - expect(style.startsWith("data:image/jpeg;base64")).eq(true); + async validateProfilePictureDisplayed() { + await expect(this.page).toHaveScreenshot({ + maxDiffPixels: 400, + }); } } diff --git a/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I2-I3---Banner-Picture---User-can-upload-banner-1.png b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I2-I3---Banner-Picture---User-can-upload-banner-1.png new file mode 100644 index 00000000..a30c336a Binary files /dev/null and b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I2-I3---Banner-Picture---User-can-upload-banner-1.png differ diff --git a/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I4---Clicking-upload-picture-on-Profile-picture-should-open-File-Browser-1.png b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I4---Clicking-upload-picture-on-Profile-picture-should-open-File-Browser-1.png new file mode 100644 index 00000000..fc30bd0f Binary files /dev/null and b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I4---Clicking-upload-picture-on-Profile-picture-should-open-File-Browser-1.png differ diff --git a/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I5---Profile-picture-appears-blank-until-custom-profile-picture-is-set-1.png b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I5---Profile-picture-appears-blank-until-custom-profile-picture-is-set-1.png new file mode 100644 index 00000000..9d3fccbc Binary files /dev/null and b/playwright/snapshots/05-settings-profile.spec.ts/Settings-Profile-Tests-I5---Profile-picture-appears-blank-until-custom-profile-picture-is-set-1.png differ diff --git a/playwright/01-pin-input.spec.ts b/playwright/specs/01-pin-input.spec.ts similarity index 95% rename from playwright/01-pin-input.spec.ts rename to playwright/specs/01-pin-input.spec.ts index 7e5518ec..bce1bb04 100644 --- a/playwright/01-pin-input.spec.ts +++ b/playwright/specs/01-pin-input.spec.ts @@ -1,25 +1,16 @@ -import { - test, - expect, - chromium, - Browser, - BrowserContext, - Page, -} from "@playwright/test"; -import { LoginPinPage } from "./PageObjects/LoginPin"; +import { test, expect } from "@playwright/test"; +import { LoginPinPage } from "../PageObjects/LoginPin"; import { faker } from "@faker-js/faker"; -import { AuthNewAccount } from "./PageObjects/AuthNewAccount"; -import { ChatsMainPage } from "./PageObjects/ChatsMain"; -import { CreateOrImportPage } from "./PageObjects/CreateOrImport"; -import { SaveRecoverySeedPage } from "./PageObjects/SaveRecoverySeed"; - -let browser: Browser, context: BrowserContext, page: Page; +import { AuthNewAccount } from "../PageObjects/AuthNewAccount"; +import { ChatsMainPage } from "../PageObjects/ChatsMain"; +import { CreateOrImportPage } from "../PageObjects/CreateOrImport"; +import { SaveRecoverySeedPage } from "../PageObjects/SaveRecoverySeed"; test.describe("Create Account and Login Tests", () => { const username = faker.person.firstName() + faker.number.int({ min: 100, max: 10000 }); const status = faker.lorem.sentence(3); - const pinNumber = "1234"; + const pinNumber = "123456"; test.beforeEach(async ({ page }) => { const createOrImport = new CreateOrImportPage(page); @@ -51,8 +42,7 @@ test.describe("Create Account and Login Tests", () => { await authNewAccount.typeOnStatus(status); await authNewAccount.buttonNewAccountCreate.click(); await loginPinPage.waitUntilPageIsLoaded(); - await loginPinPage.enterPin(pinNumber); - await loginPinPage.pinButtonConfirm.click(); + await loginPinPage.enterDefaultPin(); // Click on I Saved It await saveRecoverySeed.buttonSavedPhrase.waitFor({ state: "attached" }); @@ -216,6 +206,7 @@ test.describe("Create Account and Login Tests", () => { await loginPinPage.goToPinSettings(); await loginPinPage.clickStayUnlockedSwitch(); await expect(loginPinPage.stayUnlockedCheckbox).toBeChecked(); + await loginPinPage.goToPinSettings(); await loginPinPage.enterPin(pinNumber); await loginPinPage.pinButtonConfirm.click(); diff --git a/playwright/02-friends.spec.ts b/playwright/specs/02-friends.spec.ts similarity index 89% rename from playwright/02-friends.spec.ts rename to playwright/specs/02-friends.spec.ts index a05d16cf..b2221990 100644 --- a/playwright/02-friends.spec.ts +++ b/playwright/specs/02-friends.spec.ts @@ -5,13 +5,13 @@ import { BrowserContext, Page, } from "@playwright/test"; -import { LoginPinPage } from "./PageObjects/LoginPin"; +import { LoginPinPage } from "../PageObjects/LoginPin"; import { faker } from "@faker-js/faker"; -import { AuthNewAccount } from "./PageObjects/AuthNewAccount"; -import { ChatsMainPage } from "./PageObjects/ChatsMain"; -import { CreateOrImportPage } from "./PageObjects/CreateOrImport"; -import { FriendsScreen } from "./PageObjects/FriendsScreen"; -import { SaveRecoverySeedPage } from "./PageObjects/SaveRecoverySeed"; +import { AuthNewAccount } from "../PageObjects/AuthNewAccount"; +import { ChatsMainPage } from "../PageObjects/ChatsMain"; +import { CreateOrImportPage } from "../PageObjects/CreateOrImport"; +import { FriendsScreen } from "../PageObjects/FriendsScreen"; +import { SaveRecoverySeedPage } from "../PageObjects/SaveRecoverySeed"; let browser1: Browser, context1: BrowserContext, page1: Page; let browser2: Browser, context2: BrowserContext, page2: Page; @@ -31,7 +31,6 @@ test.describe("Friends tests", () => { faker.person.firstName() + faker.number.int({ min: 100, max: 10000 }); const status: string = faker.lorem.sentence(3); const statusTwo: string = faker.lorem.sentence(3); - const pinNumber: string = "1234"; test.beforeEach(async () => { // Setup for first user @@ -81,8 +80,7 @@ test.describe("Friends tests", () => { // Enter Pin await loginPinPage.waitUntilPageIsLoaded(); - await loginPinPage.enterPin(pinNumber); - await loginPinPage.clickConfirmButton(); + await loginPinPage.enterDefaultPin(); // Click on I Saved It await saveRecoverySeed.clickOnSavedIt(); @@ -109,8 +107,7 @@ test.describe("Friends tests", () => { // Enter a valid pin await loginPinPageSecond.waitUntilPageIsLoaded(); - await loginPinPageSecond.enterPin(pinNumber); - await loginPinPageSecond.clickConfirmButton(); + await loginPinPageSecond.enterDefaultPin(); // Click on I Saved It await saveRecoverySeedSecond.clickOnSavedIt(); diff --git a/playwright/specs/03-chats-sidebar.ts b/playwright/specs/03-chats-sidebar.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/04-marketplace.ts b/playwright/specs/04-marketplace.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/05-settings-profile.spec.ts b/playwright/specs/05-settings-profile.spec.ts new file mode 100644 index 00000000..28d596ea --- /dev/null +++ b/playwright/specs/05-settings-profile.spec.ts @@ -0,0 +1,473 @@ +import { test, expect } from "@playwright/test"; +import { LoginPinPage } from "../PageObjects/LoginPin"; +import { AuthNewAccount } from "../PageObjects/AuthNewAccount"; +import { ChatsMainPage } from "../PageObjects/ChatsMain"; +import { CreateOrImportPage } from "../PageObjects/CreateOrImport"; +import { SaveRecoverySeedPage } from "../PageObjects/SaveRecoverySeed"; +import { SettingsProfile } from "../PageObjects/Settings/SettingsProfile"; + +test.describe("Settings Profile Tests", () => { + const username = "test123"; + const status = "fixed status"; + + test.beforeEach(async ({ page }) => { + // Declare the page object implementations + const createOrImport = new CreateOrImportPage(page); + const authNewAccount = new AuthNewAccount(page); + const loginPinPage = new LoginPinPage(page); + const saveRecoverySeed = new SaveRecoverySeedPage(page); + const chatsMainPage = new ChatsMainPage(page); + + // Select Create Account + await createOrImport.navigateTo(); + await createOrImport.clickCreateNewAccount(); + + // Enter Username and Status + await authNewAccount.validateLoadingHeader(); + await authNewAccount.typeOnUsername(username); + await authNewAccount.typeOnStatus(status); + await authNewAccount.buttonNewAccountCreate.click(); + + // Enter PIN + await loginPinPage.waitUntilPageIsLoaded(); + await loginPinPage.enterDefaultPin(); + + // Click on I Saved It + await saveRecoverySeed.buttonSavedPhrase.waitFor({ state: "attached" }); + await saveRecoverySeed.clickOnSavedIt(); + await chatsMainPage.addSomeone.waitFor({ state: "visible" }); + await page.waitForURL("/chat"); + + // Go to Settings Profile page + await chatsMainPage.goToSettings(); + await page.waitForURL("/settings/profile"); + }); + + test("I1 - Banner Picture - Tooltip displayed", async ({ page }) => { + // Shows tooltip when hovering + const settingsProfile = new SettingsProfile(page); + await settingsProfile.profileBanner.hover(); + await settingsProfile.validatePseudoElementContent( + "[data-cy='profile-banner']", + "Change Banner Photo", + ); + }); + + test("I2, I3 - Banner Picture - User can upload banner", async ({ page }) => { + // User can upload a banner picture + const settingsProfile = new SettingsProfile(page); + await settingsProfile.uploadProfileBanner("cypress/fixtures/banner.jpg"); + + // Property Style is reassigned to Background Image after uploading banner + await settingsProfile.validateBannerDisplayed(); + }); + + test("I4 - Clicking upload picture on Profile picture should open File Browser", async ({ + page, + }) => { + // Profile Picture Upload Button tooltip shows "Change profile photo" + const settingsProfile = new SettingsProfile(page); + + // Validate user can upload profile pictures + await settingsProfile.uploadProfilePicture("cypress/fixtures/logo.jpg"); + await settingsProfile.validateProfilePictureDisplayed(); + + // Validate tooltip is displayed when hovering over the Profile Picture Upload Button + await settingsProfile.profilePictureUploadButton.hover(); + await settingsProfile.validateTooltipAttribute( + "[data-cy='button-file-upload']", + "Change profile photo", + ); + }); + + test("I5 - Profile picture appears blank until custom profile picture is set", async ({ + page, + }) => { + // Profile Picture should not have a src attribute + const settingsProfile = new SettingsProfile(page); + await settingsProfile.validateProfilePictureDisplayed(); + }); + + test("I6 - Username should be displayed in the Username textbox", async ({ + page, + }) => { + // Username displayed will be equal to the username assigned randomly when creating account + const settingsProfile = new SettingsProfile(page); + await expect(settingsProfile.inputSettingsProfileUsername).toHaveValue( + username, + ); + }); + + test("I7 - Clicking shortID should copy DID into clipboard", async ({ + page, + context, + }) => { + // Validate hovering on Copy ID button shows "Copy" + const settingsProfile = new SettingsProfile(page); + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await settingsProfile.validateTooltipAttribute( + "[data-tooltip='Copy']", + "Copy", + ); + + // Copy ID by just clicking on the Short ID button + await settingsProfile.copyShortID(); + + // Save copied value from clipboard into a constant + const handle = await page.evaluateHandle(() => + navigator.clipboard.readText(), + ); + const clipboardContent = await handle.jsonValue(); + + // Paste value from Clipboard into Status and assert it is the did key + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.press("Meta+v"); + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + clipboardContent, + ); + }); + + test("I8 - Short ID Context menu should allow to copy DID or Short ID intoto clipboard", async ({ + page, + context, + }) => { + // Copy ID by just right clicking on the Short ID button and selecting Copy ID + const settingsProfile = new SettingsProfile(page); + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await settingsProfile.openUserIDContextMenu(); + await settingsProfile.contextMenuOptionCopyID.click(); + + // Save copied value from clipboard into a constant + const handle = await page.evaluateHandle(() => + navigator.clipboard.readText(), + ); + const clipboardContent = await handle.jsonValue(); + + // Paste value from Clipboard into Status and assert it is the did key + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.press("Meta+v"); + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + clipboardContent, + ); + + // Copy DID by just right clicking on the Short ID button and selecting Copy DID + await settingsProfile.openUserIDContextMenu(); + await settingsProfile.contextMenuOptionCopyDID.click(); + + // Save copied value from clipboard into a constant + const handle2 = await page.evaluateHandle(() => + navigator.clipboard.readText(), + ); + const clipboardContent2 = await handle2.jsonValue(); + + // Paste value from Clipboard into Status and assert it is the did key + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.press("Meta+v"); + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + clipboardContent2, + ); + }); + + test("I9, I10 - User should be able to change username and see toast notification of change", async ({ + page, + }) => { + // User types into username and change value + const settingsProfile = new SettingsProfile(page); + const chatsMainPage = new ChatsMainPage(page); + const newUsername = "newUsername"; + await settingsProfile.inputSettingsProfileUsername.click(); + await settingsProfile.inputSettingsProfileUsername.clear(); + await settingsProfile.inputSettingsProfileUsername.fill("newUsername"); + + // Save modal is displayed, user selects cancel and username is not changed + await settingsProfile.saveControls.waitFor({ state: "visible" }); + await settingsProfile.saveControlsButtonCancel.click(); + + // Username displayed will be equal to the username assigned randomly when creating account + await expect(settingsProfile.inputSettingsProfileUsername).toHaveValue( + username, + ); + + // User types into username and change value + await settingsProfile.inputSettingsProfileUsername.click(); + await settingsProfile.inputSettingsProfileUsername.clear(); + await settingsProfile.inputSettingsProfileUsername.fill("newUsername"); + + // Save modal is displayed, user selects save and username is changed + await settingsProfile.saveControls.waitFor({ state: "visible" }); + await settingsProfile.saveControlsButtonSave.click(); + await settingsProfile.toastNotification.waitFor({ state: "visible" }); + await expect(settingsProfile.toastNotificationText).toHaveText( + "Profile Updated!", + ); + await settingsProfile.toastNotification.waitFor({ state: "detached" }); + await expect(settingsProfile.inputSettingsProfileUsername).toHaveValue( + newUsername, + ); + + // User goes to another page and returns to settings profile, username is still changed + await settingsProfile.goToFriends(); + await page.waitForURL("/friends"); + await chatsMainPage.goToSettings(); + await expect(settingsProfile.inputSettingsProfileUsername).toHaveValue( + newUsername, + ); + }); + + test("I11 - All text in Username should be selected after clicking into the text field a single time", async ({ + page, + }) => { + // User clicks on username textbox and all text is selected + const settingsProfile = new SettingsProfile(page); + await settingsProfile.assertInputTextSelected( + "[data-cy='input-settings-profile-username']", + ); + }); + + test("I12 - Highlighted border should appear when clicked into the username textbox", async ({ + page, + }) => { + // Click on Username textbox and validate border is highlighted + const settingsProfile = new SettingsProfile(page); + await settingsProfile.inputSettingsProfileUsername.focus(); + + const usernameInputBox = + await settingsProfile.inputSettingsProfileUsername.locator("xpath=.."); + await expect(usernameInputBox).toHaveCSS( + "box-shadow", + "rgb(77, 77, 255) 0px 0px 0px 1px", + ); + }); + + test("I13 - Error message should appear if user tries to input chars that are not allowed or exceeds chars amount", async ({ + page, + }) => { + // User leaves empty username - Warning message is displayed + const settingsProfile = new SettingsProfile(page); + await settingsProfile.inputSettingsProfileUsername.click(); + await settingsProfile.inputSettingsProfileUsername.clear(); + await settingsProfile.warningMessage.waitFor({ state: "visible" }); + await expect(settingsProfile.warningMessage).toHaveText( + "This field is required.", + ); + + // User types less characters than expected into username - Warning message is displayed + await settingsProfile.inputSettingsProfileUsername.fill("123"); + await expect(settingsProfile.warningMessage).toHaveText( + "Minimum length is 4 characters.", + ); + + // User types long username - Warning message is displayed + await settingsProfile.inputSettingsProfileUsername.clear(); + await settingsProfile.inputSettingsProfileUsername.fill( + "123456789012345678901234567890123", + ); + await expect(settingsProfile.warningMessage).toHaveText( + "Maximum length is 32 characters.", + ); + + // User types invalid username - Warning message is displayed + await settingsProfile.inputSettingsProfileUsername.clear(); + await settingsProfile.inputSettingsProfileUsername.fill("&*&*&&*"); + await expect(settingsProfile.warningMessage).toHaveText("Invalid format."); + }); + + test("I14 - Highlighted border should appear when user is clicked into Status textbox", async ({ + page, + }) => { + // Click on Status textbox and validate border is highlighted + const settingsProfile = new SettingsProfile(page); + await settingsProfile.inputSettingsProfileStatus.focus(); + + const usernameStatusBox = + await settingsProfile.inputSettingsProfileStatus.locator("xpath=.."); + await expect(usernameStatusBox).toHaveCSS( + "box-shadow", + "rgb(77, 77, 255) 0px 0px 0px 1px", + ); + }); + + test("I15, I16 - User should be able to change Status Message and see toast notification for update", async ({ + page, + }) => { + // User types into username and change value + const settingsProfile = new SettingsProfile(page); + const chatsMainPage = new ChatsMainPage(page); + const newStatus = "this is my new status"; + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.fill(newStatus); + + // Save modal is displayed, user selects cancel and username is not changed + await settingsProfile.saveControls.waitFor({ state: "visible" }); + await settingsProfile.saveControlsButtonCancel.click(); + + // Username displayed will be equal to the username assigned randomly when creating account + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + status, + ); + + // User types into username and change value + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.fill(newStatus); + + // Save modal is displayed, user selects save and username is changed + await settingsProfile.saveControls.waitFor({ state: "visible" }); + await settingsProfile.saveControlsButtonSave.click(); + await settingsProfile.toastNotification.waitFor({ state: "visible" }); + await expect(settingsProfile.toastNotificationText).toHaveText( + "Profile Updated!", + ); + await settingsProfile.toastNotification.waitFor({ state: "detached" }); + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + newStatus, + ); + + // User goes to another page and returns to settings profile, username is still changed + await settingsProfile.goToFriends(); + await page.waitForURL("/friends"); + await chatsMainPage.goToSettings(); + await expect(settingsProfile.inputSettingsProfileStatus).toHaveValue( + newStatus, + ); + }); + + test("I17 - All text in StatusMessage should be selected after clicking into the text field a single time", async ({ + page, + }) => { + // User clicks on status textbox and all text is selected + const settingsProfile = new SettingsProfile(page); + await settingsProfile.assertInputTextSelected( + "[data-cy='input-settings-profile-status-message']", + ); + }); + + test("I18 - Error message should appear if user inputs chars that are not allowed or exceeds limit", async ({ + page, + }) => { + // User types more characters than expected into status - Warning message is displayed + const settingsProfile = new SettingsProfile(page); + await settingsProfile.inputSettingsProfileStatus.click(); + await settingsProfile.inputSettingsProfileStatus.clear(); + await settingsProfile.inputSettingsProfileStatus.fill( + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + ); + await settingsProfile.warningMessage.waitFor({ state: "visible" }); + await expect(settingsProfile.warningMessage).toHaveText( + "Maximum length is 128 characters.", + ); + }); + + test("I19 - Status dropdown should show Online, Offline, Idle, Do not Disturb", async ({ + page, + }) => { + // Validate Settings Section contents + const settingsProfile = new SettingsProfile(page); + await expect(settingsProfile.onlineStatusSectionLabel).toHaveText("Status"); + await expect(settingsProfile.onlineStatusSectionText).toHaveText( + "Set status appearance", + ); + + // Default Status selected is Online + await settingsProfile.onlineStatusSectionSelectorCurrentlyOnline.waitFor({ + state: "attached", + }); + + // Validate list of options + let options = ["Online", "Offline", "Idle", "Do Not Disturb"]; + const actualOptions = await settingsProfile.getSelectorOptions( + "[data-cy='selector-current-status-online']", + ); + await expect(actualOptions).toEqual(options); + }); + + test("I20 - Status should show correctly depending on which status user has set", async ({ + page, + }) => { + // Change Status to Offline and validate is displayed correctly + const settingsProfile = new SettingsProfile(page); + const chatsMainPage = new ChatsMainPage(page); + await settingsProfile.selectOnlineStatus("offline"); + await settingsProfile.onlineStatusSectionSelectorCurrentlyOffline.waitFor({ + state: "visible", + }); + + // Change Status to Idle and validate is displayed correctly + await settingsProfile.selectOnlineStatus("idle"); + await settingsProfile.onlineStatusSectionSelectorCurrentlyIdle.waitFor({ + state: "visible", + }); + + // Change Status to Do not Disturb and validate is displayed correctly + await settingsProfile.selectOnlineStatus("do-not-disturb"); + await settingsProfile.onlineStatusSectionSelectorCurrentlyDoNotDisturb.waitFor( + { + state: "visible", + }, + ); + + // Go to friends page and return to Settings Profile and validate status is still the same + await settingsProfile.goToFriends(); + await page.waitForURL("/friends"); + await chatsMainPage.goToSettings(); + await page.waitForURL("/settings/profile"); + await settingsProfile.onlineStatusSectionSelectorCurrentlyDoNotDisturb.waitFor( + { + state: "visible", + }, + ); + }); + + test("I21 - Clicking Reveal Phrase should display the users Recovery Phrases", async ({ + page, + }) => { + // Validate Settings Section contents + const settingsProfile = new SettingsProfile(page); + await expect(settingsProfile.revealPhraseSectionLabel).toHaveText( + "Reveal recovery phrase", + ); + await expect(settingsProfile.revealPhraseSectionText).toHaveText( + "Click the button to reveal your recovery seed, please do not share this with anybody, it is the master-key for your account.", + ); + await expect(settingsProfile.storeRecoverySeedText).toHaveText( + "Store recovery seed on account (disable for increased security, irreversible)", + ); + + // Show Recovery Phrase and ensure is displayed now + await settingsProfile.revealPhraseSectionRevealButton.click(); + await settingsProfile.validateRecoveryPhraseIsShown(); + + // Click on Hide Phrase and validate is hidden + await settingsProfile.revealPhraseSectionHideButton.click(); + await settingsProfile.validateRecoveryPhraseIsHidden(); + }); + + // Cannot be automated for now since copy button does not perform any action + test.skip("I22 - Clicking copy should copy the Recovery Phrase to the users clipboard", async ({ + page, + }) => {}); + + // Cannot be automated for now since checkbox checked or not checked works on the same way for now + test.skip("I23 - User should be able to click checkbox to determine whether they want to store Recovery Phrase on account", async ({ + page, + }) => {}); + + test("I24 - Clicking LogOut should log user out of the account", async ({ + page, + }) => { + // Validate Settings Section contents + const settingsProfile = new SettingsProfile(page); + await expect(settingsProfile.logOutSectionLabel).toHaveText("Log Out"); + await expect(settingsProfile.logOutSectionText).toHaveText( + "Log out of the current account and return to the unlock page.", + ); + + // Click on Log Out and validate user is redirected to unlock page + await settingsProfile.logOutSectionButton.click(); + await page.waitForURL("/auth"); + }); +}); diff --git a/playwright/specs/06-settings-inventory.ts b/playwright/specs/06-settings-inventory.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/07-settings-customizations.ts b/playwright/specs/07-settings-customizations.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/08-settings-messages.ts b/playwright/specs/08-settings-messages.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/09-settings-audio-video.ts b/playwright/specs/09-settings-audio-video.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/10-settings-extensions.ts b/playwright/specs/10-settings-extensions.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/11-settings-keybinds.ts b/playwright/specs/11-settings-keybinds.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/12-settings-notifications.ts b/playwright/specs/12-settings-notifications.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/13-settings-about.ts b/playwright/specs/13-settings-about.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/14-settings-licenses.ts b/playwright/specs/14-settings-licenses.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/15-settings-developer.ts b/playwright/specs/15-settings-developer.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/16-settings-accessibility.ts b/playwright/specs/16-settings-accessibility.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/17-settings-network.ts b/playwright/specs/17-settings-network.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/18-chats-tests.ts b/playwright/specs/18-chats-tests.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/19-files-sidebar.ts b/playwright/specs/19-files-sidebar.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/20-files.ts b/playwright/specs/20-files.ts new file mode 100644 index 00000000..e69de29b diff --git a/playwright/specs/21-wallet.ts b/playwright/specs/21-wallet.ts new file mode 100644 index 00000000..e69de29b