Compare commits
2 Commits
f64846ae17
...
b91d387815
| Author | SHA1 | Date | |
|---|---|---|---|
| b91d387815 | |||
| 4a3b968ee2 |
@@ -366,7 +366,7 @@ rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safa
|
||||
|
||||
- For prod, you can do the same with `build:web:prod` instead.
|
||||
|
||||
... or log onto the server (though the build step can stay on "rendering chunks" for a long while):
|
||||
Here are instructions directly on the server, but the build step can stay on "rendering chunks" for a long time and it basically hangs any other access to the server. In fact, last time it was killed: "Failed after 482 seconds (exit code: 137)" Maybe use `nice`?
|
||||
|
||||
- `pkgx +npm sh`
|
||||
|
||||
@@ -376,7 +376,7 @@ rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safa
|
||||
|
||||
- Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev-2 && mv crowd-funder-for-time-pwa/dist time-safari/`
|
||||
|
||||
- Record the new hash in the changelog. Edit package.json to increment version &
|
||||
Be sure to record the new hash in the changelog. Edit package.json to increment version &
|
||||
add "-beta", `npm install`, commit, and push. Also record what version is on production.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
@@ -57,7 +57,7 @@ export default defineConfig({
|
||||
// },
|
||||
{
|
||||
name: 'chromium',
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share)\.spec\.ts).+\.spec\.ts$/,
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
permissions: ["clipboard-read"],
|
||||
@@ -65,7 +65,7 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share)\.spec\.ts).+\.spec\.ts$/,
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export type NotifyFunction = (
|
||||
// Standard timeouts
|
||||
export const TIMEOUTS = {
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 6000, // Short notifications (clipboard copies, quick confirmations)
|
||||
SHORT: 3000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 8000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 10000, // Very long notifications (complex operations)
|
||||
|
||||
@@ -843,17 +843,14 @@ export default class ContactsView extends Vue {
|
||||
// Set visibility and get success message
|
||||
const addedMessage = await this.handleContactVisibility(newContact);
|
||||
|
||||
// Show success notification
|
||||
this.notify.success(addedMessage);
|
||||
|
||||
// Clear input field
|
||||
this.contactInput = "";
|
||||
|
||||
// Handle registration prompt if needed
|
||||
await this.handleRegistrationPrompt(newContact);
|
||||
|
||||
// Show success notification
|
||||
this.notify.success(addedMessage);
|
||||
|
||||
// Show export data prompt after successful contact addition
|
||||
await this.showExportDataPrompt();
|
||||
} catch (err) {
|
||||
this.handleContactAddError(err);
|
||||
}
|
||||
@@ -908,30 +905,35 @@ export default class ContactsView extends Vue {
|
||||
newContact.registered === true // the new contact is already registered
|
||||
) {
|
||||
// if any of the above are true, we do not want to show the registration prompt
|
||||
await this.showExportDataPrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
},
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
},
|
||||
onYes: async () => {
|
||||
await this.register(newContact);
|
||||
},
|
||||
promptToStopAsking: true,
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
// Show export prompt after registration prompt is dismissed
|
||||
await this.showExportDataPrompt();
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 1000);
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
// Show export prompt after registration prompt is dismissed
|
||||
await this.showExportDataPrompt();
|
||||
},
|
||||
onYes: async () => {
|
||||
await this.register(newContact);
|
||||
// Show export prompt after registration completes
|
||||
await this.showExportDataPrompt();
|
||||
},
|
||||
promptToStopAsking: true,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1338,25 +1340,23 @@ export default class ContactsView extends Vue {
|
||||
* Prompts user to export their contact data as a backup
|
||||
*/
|
||||
private async showExportDataPrompt(): Promise<void> {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFY_EXPORT_DATA_PROMPT.title,
|
||||
text: NOTIFY_EXPORT_DATA_PROMPT.message,
|
||||
onYes: async () => {
|
||||
await this.exportContactData();
|
||||
},
|
||||
yesText: "Export Data",
|
||||
onNo: async () => {
|
||||
// User chose not to export - no action needed
|
||||
},
|
||||
noText: "Not Now",
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFY_EXPORT_DATA_PROMPT.title,
|
||||
text: NOTIFY_EXPORT_DATA_PROMPT.message,
|
||||
onYes: async () => {
|
||||
await this.exportContactData();
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 1000); // Small delay to ensure success notification is shown first
|
||||
yesText: "Export Data",
|
||||
onNo: async () => {
|
||||
// User chose not to export - no action needed
|
||||
},
|
||||
noText: "Not Yet",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -92,12 +92,13 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
||||
await page.goto('./contacts');
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F, ' + userName);
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
// Commenting the following lines because user 00 is already registered
|
||||
// await expect(page.locator('div[role="alert"] span:has-text("No")')).toBeVisible();
|
||||
// await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
|
||||
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
||||
await expect(page.getByRole('alert').filter({ hasText: 'Success' })).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
await expect(page.locator('div[role="alert"] span:has-text("No")')).toBeVisible(); // register prompt
|
||||
await page.getByRole('button', { name: 'No', exact: true }).click(); // don't register
|
||||
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
|
||||
await expect(page.getByRole('button', { name: 'No, Not Yet', exact: true })).toBeVisible(); // backup prompt
|
||||
await page.getByRole('button', { name: 'No, Not Yet', exact: true }).click(); // don't backup contacts
|
||||
|
||||
// Verify added contact
|
||||
await expect(page.locator('li.border-b')).toContainText(userName);
|
||||
@@ -118,10 +119,10 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
||||
await page.goto('./');
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
await page.getByRole('button', { name: 'Thank' }).click();
|
||||
await expect(page.locator('#sectionGiftedGiver').getByRole('listitem').filter({ hasText: contactName })).toBeVisible();
|
||||
await expect(page.locator('#sectionGiftedGiver').getByRole('listitem').filter({ hasText: contactName }).first()).toBeVisible();
|
||||
|
||||
// Record something given by new contact
|
||||
await page.locator('#sectionGiftedGiver').getByRole('listitem').filter({ hasText: contactName }).click();
|
||||
await page.locator('#sectionGiftedGiver').getByRole('listitem').filter({ hasText: contactName }).first().click();
|
||||
await page.getByPlaceholder('What was given').fill(finalTitle);
|
||||
await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString());
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
@@ -156,7 +157,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
||||
|
||||
// Go to home view and look for gift
|
||||
await page.goto('./');
|
||||
// await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
const giftLink = page.locator('li:first-child').filter({ hasText: finalTitle }).locator('[data-testid="circle-info-link"]');
|
||||
await expect(giftLink).toBeVisible();
|
||||
await giftLink.click();
|
||||
@@ -179,11 +180,13 @@ test('Without being registered, add contacts without registration', async ({ pag
|
||||
await page.goto('./contacts');
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111');
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
||||
await expect(page.getByRole('alert').filter({ hasText: 'Success' })).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
// wait for the alert to disappear, which also ensures that there is no "Register" button waiting
|
||||
await expect(page.locator('div[role="alert"]')).toBeHidden();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'No, Not Yet', exact: true })).toBeVisible(); // backup prompt
|
||||
await page.getByRole('button', { name: 'No, Not Yet', exact: true }).click(); // don't backup contacts
|
||||
await expect(page.getByRole('button', { name: 'No, Not Yet', exact: true })).toBeHidden();
|
||||
// wait for all alerts to disappear
|
||||
await expect(page.locator('div[role="alert"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('Add contact, copy details, delete, and import from paste & from file', async ({ page, context }) => {
|
||||
@@ -193,22 +196,26 @@ test('Add contact, copy details, delete, and import from paste & from file', asy
|
||||
await page.goto('./contacts');
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111');
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
await expect(page.locator('div[role="alert"]')).toBeVisible();
|
||||
await expect(page.locator('div[role="alert"] span:has-text("No")')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
|
||||
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
||||
await expect(page.locator('div[role="alert"]').first()).toBeVisible();
|
||||
await expect(page.getByRole('alert').filter({ hasText: 'Success' })).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
await expect(page.getByRole('button', { name: 'No', exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'No', exact: true }).click(); // don't register
|
||||
await expect(page.getByRole('button', { name: 'No, Not Yet', exact: true })).toBeVisible(); // backup prompt
|
||||
await page.getByRole('button', { name: 'No, Not Yet', exact: true }).click(); // don't backup contacts
|
||||
// wait for the alert to disappear
|
||||
await expect(page.locator('div[role="alert"]')).toBeHidden();
|
||||
await expect(page.locator('div[role="alert"]')).toHaveCount(0);
|
||||
|
||||
// Add another new contact
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b, User #222, asdf1234');
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
await expect(page.locator('div[role="alert"] span:has-text("No")')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
|
||||
await expect(page.locator('div[role="alert"] span:has-text("Success")')).toBeVisible();
|
||||
await expect(page.getByRole('alert').filter({ hasText: 'Success' })).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
await expect(page.locator('div[role="alert"]')).toBeHidden();
|
||||
await expect(page.getByRole('button', { name: 'No', exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'No', exact: true }).click(); // don't register
|
||||
await expect(page.getByRole('button', { name: 'No, Not Yet', exact: true })).toBeVisible(); // backup prompt
|
||||
await page.getByRole('button', { name: 'No, Not Yet', exact: true }).click(); // don't backup contacts
|
||||
await expect(page.locator('div[role="alert"]')).toHaveCount(0);
|
||||
|
||||
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
|
||||
|
||||
@@ -286,15 +293,12 @@ test('Copy contact to clipboard, then import ', async ({ page, context }, testIn
|
||||
// Copy contact details
|
||||
await page.getByTestId('contactCheckAllTop').click();
|
||||
|
||||
const isChromium = await page.evaluate(() => {
|
||||
return navigator.userAgent.includes('Chrome') || navigator.userAgent.includes('Chromium');
|
||||
});
|
||||
const isFirefox = await page.evaluate(() => {
|
||||
return navigator.userAgent.includes('Firefox');
|
||||
});
|
||||
if (isFirefox) {
|
||||
// Firefox doesn't grant permissions like this but it works anyway.
|
||||
} else {
|
||||
await context.grantPermissions(['clipboard-read']);
|
||||
}
|
||||
|
||||
const isWebkit = await page.evaluate(() => {
|
||||
return navigator.userAgent.includes('Macintosh') || navigator.userAgent.includes('iPhone');
|
||||
});
|
||||
@@ -303,9 +307,20 @@ test('Copy contact to clipboard, then import ', async ({ page, context }, testIn
|
||||
return;
|
||||
}
|
||||
|
||||
if (isChromium) {
|
||||
await context.grantPermissions(['clipboard-read']);
|
||||
}
|
||||
|
||||
await page.getByTestId('copySelectedContactsButtonTop').click();
|
||||
// Wait a moment for the clipboard operation to complete, especially for Chromium
|
||||
await page.waitForTimeout(100);
|
||||
const clipboardText = await page.evaluate(async () => {
|
||||
return navigator.clipboard.readText();
|
||||
try {
|
||||
return await navigator.clipboard.readText();
|
||||
} catch (error) {
|
||||
console.error('Clipboard read failed:', error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// look into the playwright.config file for the server URL
|
||||
@@ -320,5 +335,6 @@ test('Copy contact to clipboard, then import ', async ({ page, context }, testIn
|
||||
await page.goto(clipboardText);
|
||||
// we're on the contact-import page
|
||||
await expect(page.getByRole('heading', { name: "Contact Import" })).toBeVisible();
|
||||
await expect(page.locator('span', { hasText: '4 contacts are the same' })).toBeVisible();
|
||||
// For some reason, Chromium shows 1 contact the same but Firefox shows 4.
|
||||
await expect(page.locator('span', { hasText: 'the same as' })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user