diff --git a/electron/src/setup.ts b/electron/src/setup.ts index 1af979bd..55d79f1a 100644 --- a/electron/src/setup.ts +++ b/electron/src/setup.ts @@ -225,8 +225,8 @@ export function setupContentSecurityPolicy(customScheme: string): void { ...details.responseHeaders, 'Content-Security-Policy': [ electronIsDev - ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data: https: http:; style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com; font-src ${customScheme}://* https://fonts.gstatic.com data:` - : `default-src ${customScheme}://* 'unsafe-inline' data: https:; style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com; font-src ${customScheme}://* https://fonts.gstatic.com data:`, + ? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data: https: http:; style-src ${customScheme}://* 'unsafe-inline'; font-src ${customScheme}://* data:` + : `default-src ${customScheme}://* 'unsafe-inline' data: https:; style-src ${customScheme}://* 'unsafe-inline'; font-src ${customScheme}://* data:`, ], }, }); diff --git a/scripts/download-fonts.sh b/scripts/download-fonts.sh new file mode 100755 index 00000000..7b7a1c35 --- /dev/null +++ b/scripts/download-fonts.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# Download Work Sans font files locally +# This script downloads the Work Sans font family and creates local CSS + +FONT_DIR="src/assets/fonts" +CSS_FILE="src/assets/styles/fonts.css" + +# Create fonts directory +mkdir -p "$FONT_DIR" + +# Download Work Sans font files +echo "Downloading Work Sans font files..." + +# Regular weights (300, 400, 500, 600, 700) +curl -o "$FONT_DIR/WorkSans-Light.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32KxfXNig.ttf" +curl -o "$FONT_DIR/WorkSans-Regular.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K0nXNig.ttf" +curl -o "$FONT_DIR/WorkSans-Medium.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K3vXNig.ttf" +curl -o "$FONT_DIR/WorkSans-SemiBold.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K5fQNig.ttf" +curl -o "$FONT_DIR/WorkSans-Bold.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY_z_wNahGAdqQ43RhVcIgYT2Xz5u32K67QNig.ttf" + +# Italic weights (300, 400, 500, 600, 700) +curl -o "$FONT_DIR/WorkSans-LightItalic.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGUgGsJow.ttf" +curl -o "$FONT_DIR/WorkSans-Italic.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGU3msJow.ttf" +curl -o "$FONT_DIR/WorkSans-MediumItalic.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGU7GsJow.ttf" +curl -o "$FONT_DIR/WorkSans-SemiBoldItalic.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGUAGwJow.ttf" +curl -o "$FONT_DIR/WorkSans-BoldItalic.ttf" "https://fonts.gstatic.com/s/worksans/v23/QGY9z_wNahGAdqQ43Rh_ebrnlwyYfEPxPoGUOWwJow.ttf" + +echo "Font files downloaded to $FONT_DIR" + +# Create local CSS file +cat > "$CSS_FILE" << 'EOF' +/* Work Sans font family - locally hosted */ + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('../fonts/WorkSans-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../fonts/WorkSans-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('../fonts/WorkSans-Medium.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('../fonts/WorkSans-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../fonts/WorkSans-Bold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url('../fonts/WorkSans-LightItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('../fonts/WorkSans-Italic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url('../fonts/WorkSans-MediumItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url('../fonts/WorkSans-SemiBoldItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url('../fonts/WorkSans-BoldItalic.ttf') format('truetype'); +} +EOF + +echo "Local font CSS created at $CSS_FILE" +echo "Don't forget to update tailwind.css to import this file instead of Google Fonts!" \ No newline at end of file diff --git a/src/assets/fonts/WorkSans-Bold.ttf b/src/assets/fonts/WorkSans-Bold.ttf new file mode 100644 index 00000000..e6281999 Binary files /dev/null and b/src/assets/fonts/WorkSans-Bold.ttf differ diff --git a/src/assets/fonts/WorkSans-BoldItalic.ttf b/src/assets/fonts/WorkSans-BoldItalic.ttf new file mode 100644 index 00000000..839837fb Binary files /dev/null and b/src/assets/fonts/WorkSans-BoldItalic.ttf differ diff --git a/src/assets/fonts/WorkSans-Italic.ttf b/src/assets/fonts/WorkSans-Italic.ttf new file mode 100644 index 00000000..df0796a4 Binary files /dev/null and b/src/assets/fonts/WorkSans-Italic.ttf differ diff --git a/src/assets/fonts/WorkSans-Light.ttf b/src/assets/fonts/WorkSans-Light.ttf new file mode 100644 index 00000000..9f571575 Binary files /dev/null and b/src/assets/fonts/WorkSans-Light.ttf differ diff --git a/src/assets/fonts/WorkSans-LightItalic.ttf b/src/assets/fonts/WorkSans-LightItalic.ttf new file mode 100644 index 00000000..fc3774af Binary files /dev/null and b/src/assets/fonts/WorkSans-LightItalic.ttf differ diff --git a/src/assets/fonts/WorkSans-Medium.ttf b/src/assets/fonts/WorkSans-Medium.ttf new file mode 100644 index 00000000..13cd7b0f Binary files /dev/null and b/src/assets/fonts/WorkSans-Medium.ttf differ diff --git a/src/assets/fonts/WorkSans-MediumItalic.ttf b/src/assets/fonts/WorkSans-MediumItalic.ttf new file mode 100644 index 00000000..004f3196 Binary files /dev/null and b/src/assets/fonts/WorkSans-MediumItalic.ttf differ diff --git a/src/assets/fonts/WorkSans-Regular.ttf b/src/assets/fonts/WorkSans-Regular.ttf new file mode 100644 index 00000000..d4af1a8e Binary files /dev/null and b/src/assets/fonts/WorkSans-Regular.ttf differ diff --git a/src/assets/fonts/WorkSans-SemiBold.ttf b/src/assets/fonts/WorkSans-SemiBold.ttf new file mode 100644 index 00000000..e4867b9b Binary files /dev/null and b/src/assets/fonts/WorkSans-SemiBold.ttf differ diff --git a/src/assets/fonts/WorkSans-SemiBoldItalic.ttf b/src/assets/fonts/WorkSans-SemiBoldItalic.ttf new file mode 100644 index 00000000..f6dfde75 Binary files /dev/null and b/src/assets/fonts/WorkSans-SemiBoldItalic.ttf differ diff --git a/src/assets/styles/fonts.css b/src/assets/styles/fonts.css new file mode 100644 index 00000000..7e8d12ca --- /dev/null +++ b/src/assets/styles/fonts.css @@ -0,0 +1,81 @@ +/* Work Sans font family - locally hosted */ + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('../fonts/WorkSans-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../fonts/WorkSans-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('../fonts/WorkSans-Medium.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('../fonts/WorkSans-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../fonts/WorkSans-Bold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url('../fonts/WorkSans-LightItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('../fonts/WorkSans-Italic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url('../fonts/WorkSans-MediumItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url('../fonts/WorkSans-SemiBoldItalic.ttf') format('truetype'); +} + +@font-face { + font-family: 'Work Sans'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url('../fonts/WorkSans-BoldItalic.ttf') format('truetype'); +} diff --git a/src/assets/styles/tailwind.css b/src/assets/styles/tailwind.css index ad0b3d58..60f6579d 100644 --- a/src/assets/styles/tailwind.css +++ b/src/assets/styles/tailwind.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap'); +@import './fonts.css'; @tailwind base; @tailwind components; @tailwind utilities; diff --git a/test-playwright/60-new-activity.spec.ts b/test-playwright/60-new-activity.spec.ts index 8c1503bc..daffbb30 100644 --- a/test-playwright/60-new-activity.spec.ts +++ b/test-playwright/60-new-activity.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import { switchToUser, getTestUserData } from './testUtils'; +import { switchToUser, getTestUserData, importUserFromAccount } from './testUtils'; test('New offers for another user', async ({ page }) => { await page.goto('./'); @@ -15,30 +15,9 @@ test('New offers for another user', async ({ page }) => { await page.getByTestId('closeOnboardingAndFinish').click(); await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden(); - // Navigate to AccountViewView to use the Identity Switcher - await page.goto('./account'); - - // Click "Show Advanced Settings" to reveal the identity switcher - await page.getByTestId('advancedSettings').click(); - - // Use the identity switcher to add User Zero - await page.locator('#switch-identity-link').click(); - await page.locator('#start-link').click(); - - // Select "You have a seed" option - await page.getByText('You have a seed').click(); - - // Get User Zero's seed phrase using the new method - const userZeroData = getTestUserData('00'); - - // Enter User Zero's seed phrase - await page.getByPlaceholder('Seed Phrase').fill(userZeroData.seedPhrase); - - await page.getByRole('button', { name: 'Import' }).click(); - - // Wait for import to complete - await page.waitForLoadState('networkidle'); - + // Become User Zero + await importUserFromAccount(page, "00"); + // As User Zero, add the auto-created DID as a contact await page.goto('./contacts'); await page.getByPlaceholder('URL or DID, Name, Public Key').fill(autoCreatedDid + ', A Friend'); diff --git a/test-playwright/testUtils.ts b/test-playwright/testUtils.ts index 5733f4ea..fc2522fc 100644 --- a/test-playwright/testUtils.ts +++ b/test-playwright/testUtils.ts @@ -1,48 +1,87 @@ -import { expect, Page } from '@playwright/test'; +import { expect, Page } from "@playwright/test"; // Get test user data based on the ID. // '01' -> user 111 // otherwise -> user 000 // (... which is a weird convention but I haven't taken the time to change it) -export function getTestUserData(id?: string): { seedPhrase: string, userName: string, did: string } { - switch(id) { - case '01': +export function getTestUserData(id?: string): { + seedPhrase: string; + userName: string; + did: string; +} { + switch (id) { + case "01": return { - seedPhrase: 'island fever beef wine urban aim vacant quit afford total poem flame service calm better adult neither color gaze forum month sister imitate excite', - userName: 'User One', - did: 'did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39' + seedPhrase: + "island fever beef wine urban aim vacant quit afford total poem flame service calm better adult neither color gaze forum month sister imitate excite", + userName: "User One", + did: "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39", }; default: // to user 00 return { - seedPhrase: 'rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage', - userName: 'User Zero', - did: 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F' + seedPhrase: + "rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage", + userName: "User Zero", + did: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F", }; } } +export async function importUserFromAccount(page: Page, id?: string): Promise { + // Navigate to AccountViewView to use the Identity Switcher + await page.goto("./account"); + + // Click "Show Advanced Settings" to reveal the identity switcher + await page.getByTestId("advancedSettings").click(); + + // Use the identity switcher to add User Zero + await page.locator("#switch-identity-link").click(); + await page.locator("#start-link").click(); + + // Select "You have a seed" option + await page.getByText("You have a seed").click(); + + // Get User Zero's seed phrase using the new method + const userZeroData = getTestUserData(id); + + // Enter User Zero's seed phrase + await page.getByPlaceholder("Seed Phrase").fill(userZeroData.seedPhrase); + + await page.getByRole("button", { name: "Import" }).click(); + + // Wait for import to complete + //await page.waitForLoadState("networkidle"); + + return userZeroData.did; +} + // Import the seed and switch to the user based on the ID. export async function importUser(page: Page, id?: string): Promise { const userData = getTestUserData(id); const { seedPhrase, userName, did } = userData; // Import ID - await page.goto('./start'); - await page.getByText('You have a seed').click(); - await page.getByPlaceholder('Seed Phrase').fill(seedPhrase); - await page.getByRole('button', { name: 'Import' }).click(); + await page.goto("./start"); + await page.getByText("You have a seed").click(); + await page.getByPlaceholder("Seed Phrase").fill(seedPhrase); + await page.getByRole("button", { name: "Import" }).click(); // Check DID - await expect(page.getByRole('code')).toContainText(did); + await expect(page.getByRole("code")).toContainText(did); // ... and ensure the app retrieves the registration status - await expect(page.locator('#sectionUsageLimits').getByText('Checking')).toBeHidden(); + await expect( + page.locator("#sectionUsageLimits").getByText("Checking") + ).toBeHidden(); return did; } -export async function importUserAndCloseOnboarding(page: Page, id?: string): Promise { +export async function importUserAndCloseOnboarding( + page: Page, + id?: string +): Promise { const did = await importUser(page, id); - await page.goto('./'); - await page.getByTestId('closeOnboardingAndFinish').click(); + await page.goto("./"); + await page.getByTestId("closeOnboardingAndFinish").click(); return did; } @@ -51,14 +90,29 @@ export async function switchToUser(page: Page, did: string): Promise { // This is the direct approach but users have to tap on things so we'll do that instead. //await page.goto('./identity-switcher'); - await page.goto('./account'); - await page.getByRole('heading', { name: 'Advanced' }).click(); - await page.getByRole('link', { name: 'Switch Identifier' }).click(); + await page.goto("./account"); + + // Wait for the page to load and the advanced settings element to be visible + await page.waitForLoadState('networkidle'); + await page.getByTestId("advancedSettings").waitFor({ state: 'visible' }); + + const switchIdentityLink = page.locator("#switch-identity-link"); + + if (await switchIdentityLink.isHidden()) { + console.log("Switch identity link is hidden, clicking advanced settings"); + await page.getByTestId("advancedSettings").click(); + await switchIdentityLink.click(); + } else { + console.log("Switch identity link is visible, clicking it"); + await switchIdentityLink.click(); + } + const didElem = await page.locator(`code:has-text("${did}")`); await didElem.isVisible(); await didElem.click(); + // wait for the switch to happen and the account page to fully load - await page.getByTestId('didWrapper').locator('code:has-text("did:")'); + await page.getByTestId("didWrapper").locator('code:has-text("did:")'); } function createContactName(did: string): string { @@ -66,24 +120,32 @@ function createContactName(did: string): string { } export async function deleteContact(page: Page, did: string): Promise { - await page.goto('./contacts'); + await page.goto("./contacts"); const contactName = createContactName(did); // go to the detail page for this contact - await page.locator(`li[data-testid="contactListItem"] h2:has-text("${contactName}") + div svg.fa-circle-info`).click(); + await page + .locator( + `li[data-testid="contactListItem"] h2:has-text("${contactName}") + div svg.fa-circle-info` + ) + .click(); // delete the contact - await page.locator('button > svg.fa-trash-can').click(); + await page.locator("button > svg.fa-trash-can").click(); await page.locator('div[role="alert"] button:has-text("Yes")').click(); // for some reason, .isHidden() (without expect) doesn't work - await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden(); + await expect( + page.locator('div[role="alert"] button:has-text("Yes")') + ).toBeHidden(); } export async function generateNewEthrUser(page: Page): Promise { - await page.goto('./start'); - await page.getByTestId('newSeed').click(); + await page.goto("./start"); + await page.getByTestId("newSeed").click(); await expect(page.locator('span:has-text("Created")')).toBeVisible(); - await page.goto('./account'); - const didElem = await page.getByTestId('didWrapper').locator('code:has-text("did:")'); + await page.goto("./account"); + const didElem = await page + .getByTestId("didWrapper") + .locator('code:has-text("did:")'); const newDid = await didElem.innerText(); return newDid; } @@ -93,28 +155,36 @@ export async function generateNewEthrUser(page: Page): Promise { export async function generateAndRegisterEthrUser(page: Page): Promise { const newDid = await generateNewEthrUser(page); - await importUser(page, '000'); // switch to user 000 + await importUser(page, "000"); // switch to user 000 - await page.goto('./contacts'); + await page.goto("./contacts"); const contactName = createContactName(newDid); - await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${newDid}, ${contactName}`); - await page.locator('button > svg.fa-plus').click(); + await page + .getByPlaceholder("URL or DID, Name, Public Key") + .fill(`${newDid}, ${contactName}`); + await page.locator("button > svg.fa-plus").click(); // register them await page.locator('div[role="alert"] button:has-text("Yes")').click(); // wait for it to disappear because the next steps may depend on alerts being gone - await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden(); - await expect(page.locator('li', { hasText: contactName })).toBeVisible(); + await expect( + page.locator('div[role="alert"] button:has-text("Yes")') + ).toBeHidden(); + await expect(page.locator("li", { hasText: contactName })).toBeVisible(); return newDid; } // Function to generate a random string of specified length export async function generateRandomString(length: number): Promise { - return Math.random().toString(36).substring(2, 2 + length); + return Math.random() + .toString(36) + .substring(2, 2 + length); } // Function to create an array of unique strings -export async function createUniqueStringsArray(count: number): Promise { +export async function createUniqueStringsArray( + count: number +): Promise { const stringsArray: string[] = []; const stringLength = 16; @@ -127,7 +197,9 @@ export async function createUniqueStringsArray(count: number): Promise } // Function to create an array of two-digit non-zero numbers -export async function createRandomNumbersArray(count: number): Promise { +export async function createRandomNumbersArray( + count: number +): Promise { const numbersArray: number[] = []; for (let i = 0; i < count; i++) { @@ -139,35 +211,37 @@ export async function createRandomNumbersArray(count: number): Promise } export function isLinuxEnvironment() { - return process.platform === 'linux'; + return process.platform === "linux"; } export function getOSSpecificTimeout(): number { // Increase base timeout for Linux - const isLinux = process.platform === 'linux'; + const isLinux = process.platform === "linux"; return isLinux ? 180000 : 60000; // 3 minutes for Linux, 1 minute for others } export function getOSSpecificConfig() { - if (isLinuxEnvironment()) { - return { - retries: 2, - timeout: 90000, // Increased global timeout - expect: { - timeout: 30000 // Increased expect timeout - }, - // Add video recording for failed tests on Linux - use: { - video: 'retain-on-failure', - trace: 'retain-on-failure' - } - }; - } - return {}; + if (isLinuxEnvironment()) { + return { + retries: 2, + timeout: 90000, // Increased global timeout + expect: { + timeout: 30000, // Increased expect timeout + }, + // Add video recording for failed tests on Linux + use: { + video: "retain-on-failure", + trace: "retain-on-failure", + }, + }; + } + return {}; } // Add helper for test grouping export function isResourceIntensiveTest(testPath: string): boolean { - return testPath.includes('35-record-gift-from-image-share') || - testPath.includes('40-add-contact'); + return ( + testPath.includes("35-record-gift-from-image-share") || + testPath.includes("40-add-contact") + ); }