Compare commits

..

6 Commits

19 changed files with 164 additions and 135 deletions

View File

@@ -362,7 +362,7 @@ Prerequisites: macOS with Xcode installed
4. Bump the version to match Android & package.json:
```
cd ios/App && xcrun agvtool new-version 35 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.2;/g" App.xcodeproj/project.pbxproj && cd -
cd ios/App && xcrun agvtool new-version 36 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.3;/g" App.xcodeproj/project.pbxproj && cd -
# Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5
```
@@ -385,11 +385,12 @@ Prerequisites: macOS with Xcode installed
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
* If it fails with `building for 'iOS', but linking in dylib (.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run XCode outside that terminal (ie. not with `npx cap open ios`).
* Click Distribute -> App Store Connect
* In AppStoreConnect, add the build to the distribution: remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
* In AppStoreConnect, add the build to the distribution. You may have to remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
* May have to go to App Review, click Submission, then hover over the build and click "-".
* It can take 15 minutes for the build to show up in the list of builds.
* You'll probably have to "Manage" something about encryption, disallowed in France.
* Then "Save" and "Add to Review" and "Resubmit to App Review".
* Eventually it'll be "Ready for Distribution" which means
### Android Build

View File

@@ -6,9 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.0.3] - 2025.07.12
### Changed
- Photo is pinned to profile mode
### Fixed
- Deep link URLs (and other prod settings)
- Error in BVC begin view
## [1.0.2] - 2025.06.20 - 276e0a741bc327de3380c4e508cccb7fee58c06d

View File

@@ -55,7 +55,7 @@ See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
Application icons are in the `assets` directory, processed by the `capacitor-assets` command.
To add a Font Awesome icon, add to main.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name.
To add a Font Awesome icon, add to fontawesome.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name.
## Other

View File

@@ -31,8 +31,8 @@ android {
applicationId "app.timesafari.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 35
versionName "1.0.2"
versionCode 36
versionName "1.0.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View File

@@ -403,7 +403,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 35;
CURRENT_PROJECT_VERSION = 36;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -413,7 +413,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -430,7 +430,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 35;
CURRENT_PROJECT_VERSION = 36;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -440,7 +440,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "timesafari",
"version": "1.0.3-beta",
"version": "1.0.4-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "timesafari",
"version": "1.0.3-beta",
"version": "1.0.4-beta",
"dependencies": {
"@capacitor-community/sqlite": "6.0.2",
"@capacitor-mlkit/barcode-scanning": "^6.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "timesafari",
"version": "1.0.3-beta",
"version": "1.0.4-beta",
"description": "Time Safari Application",
"author": {
"name": "Time Safari Team"

View File

@@ -36,12 +36,19 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "retain-on-failure",
// Add request logging
logger: {
isEnabled: (name, severity) => severity === 'error' || name === 'api',
log: (name, severity, message, args) => console.log(`${severity}: ${message}`, args)
}
},
/* Configure projects for major browsers */
projects: [
// {
// name: 'chromium-serial',
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
// use: {
// ...devices['Desktop Chrome'],
// permissions: ["clipboard-read"],
@@ -50,11 +57,13 @@ export default defineConfig({
// },
// {
// name: 'firefox-serial',
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
// use: { ...devices['Desktop Firefox'] },
// workers: 1,
// },
{
name: 'chromium',
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
use: {
...devices['Desktop Chrome'],
permissions: ["clipboard-read"],
@@ -62,6 +71,7 @@ export default defineConfig({
},
{
name: 'firefox',
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
use: { ...devices['Desktop Firefox'] },
},

View File

@@ -27,37 +27,8 @@
*/
import { z } from "zod";
// Add a union type of all valid route paths
export const VALID_DEEP_LINK_ROUTES = [
// note that similar lists are below in deepLinkSchemas and in src/services/deepLinks.ts
"claim",
"claim-add-raw",
"claim-cert",
"confirm-gift",
"contact-import",
"did",
"invite-one-accept",
"onboard-meeting-setup",
"project",
"user-profile",
] as const;
// Create a type from the array
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
// Update your schema definitions to use this type
export const baseUrlSchema = z.object({
scheme: z.literal("timesafari"),
path: z.string(),
queryParams: z.record(z.string()).optional(),
});
// Use the type to ensure route validation
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES);
// Parameter validation schemas for each route type
export const deepLinkSchemas = {
// note that similar lists are above in VALID_DEEP_LINK_ROUTES and in src/services/deepLinks.ts
claim: z.object({
id: z.string(),
}),
@@ -72,16 +43,24 @@ export const deepLinkSchemas = {
"confirm-gift": z.object({
id: z.string(),
}),
"contact-edit": z.object({
did: z.string(),
}),
"contact-import": z.object({
jwt: z.string(),
}),
contacts: z.object({
contactJwt: z.string().optional(),
inviteJwt: z.string().optional(),
}),
did: z.object({
did: z.string(),
}),
"invite-one-accept": z.object({
jwt: z.string(),
// optional because A) it could be a query param, and B) the page displays an input if things go wrong
jwt: z.string().optional(),
}),
"onboard-meeting-setup": z.object({
"onboard-meeting-members": z.object({
id: z.string(),
}),
project: z.object({
@@ -92,6 +71,19 @@ export const deepLinkSchemas = {
}),
};
// Create a type from the array
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
// Update your schema definitions to use this type
export const baseUrlSchema = z.object({
scheme: z.literal("timesafari"),
path: z.string(),
queryParams: z.record(z.string()).optional(),
});
// Add a union type of all valid route paths
export const VALID_DEEP_LINK_ROUTES = Object.keys(deepLinkSchemas) as readonly (keyof typeof deepLinkSchemas)[];
export type DeepLinkParams = {
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
};
@@ -100,3 +92,6 @@ export interface DeepLinkError extends Error {
code: string;
details?: unknown;
}
// Use the type to ensure route validation
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES as [string, ...string[]]);

View File

@@ -72,12 +72,11 @@ const handleDeepLink = async (data: { url: string }) => {
await deepLinkHandler.handleDeepLink(data.url);
} catch (error) {
logger.error("[DeepLink] Error handling deep link: ", error);
handleApiError(
{
message: error instanceof Error ? error.message : safeStringify(error),
} as AxiosError,
"deep-link",
);
let message: string = error instanceof Error ? error.message : safeStringify(error);
if (data.url) {
message += `\nURL: ${data.url}`;
}
handleApiError({ message } as AxiosError, "deep-link");
}
};

View File

@@ -73,6 +73,11 @@ const routes: Array<RouteRecordRaw> = [
name: "contacts",
component: () => import("../views/ContactsView.vue"),
},
{
path: "/database-migration",
name: "database-migration",
component: () => import("../views/DatabaseMigration.vue"),
},
{
path: "/did/:did?",
name: "did",
@@ -139,8 +144,9 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("../views/InviteOneView.vue"),
},
{
// optional because A) it could be a query param, and B) the page displays an input if things go wrong
path: "/invite-one-accept/:jwt?",
name: "InviteOneAcceptView",
name: "invite-one-accept",
component: () => import("../views/InviteOneAcceptView.vue"),
},
{
@@ -148,11 +154,6 @@ const routes: Array<RouteRecordRaw> = [
name: "logs",
component: () => import("../views/LogView.vue"),
},
{
path: "/database-migration",
name: "database-migration",
component: () => import("../views/DatabaseMigration.vue"),
},
{
path: "/new-activity",
name: "new-activity",

View File

@@ -44,6 +44,8 @@
*/
import { Router } from "vue-router";
import { z } from "zod";
import {
deepLinkSchemas,
baseUrlSchema,
@@ -53,6 +55,31 @@ import {
import { logConsoleAndDb } from "../db/databaseUtil";
import type { DeepLinkError } from "../interfaces/deepLinks";
// Helper function to extract the first key from a Zod object schema
function getFirstKeyFromZodObject(schema: z.ZodObject<any>): string | undefined {
const shape = schema.shape;
const keys = Object.keys(shape);
return keys.length > 0 ? keys[0] : undefined;
}
/**
* Maps deep link routes to their corresponding Vue router names and optional parameter keys.
*
* It's an object where keys are the deep link routes and values are objects with 'name' and 'paramKey'.
*
* The paramKey is used to extract the parameter from the route path,
* because "router.replace" expects the right parameter name for the route.
*/
export const ROUTE_MAP: Record<string, { name: string; paramKey?: string }> =
Object.entries(deepLinkSchemas).reduce((acc, [routeName, schema]) => {
const paramKey = getFirstKeyFromZodObject(schema as z.ZodObject<any>);
acc[routeName] = {
name: routeName,
paramKey
};
return acc;
}, {} as Record<string, { name: string; paramKey?: string }>);
/**
* Handles processing and routing of deep links in the application.
* Provides validation, error handling, and routing for deep link URLs.
@@ -69,30 +96,7 @@ export class DeepLinkHandler {
}
/**
* Maps deep link routes to their corresponding Vue router names and optional parameter keys.
*
* The paramKey is used to extract the parameter from the route path,
* because "router.replace" expects the right parameter name for the route.
* The default is "id".
*/
private readonly ROUTE_MAP: Record<
string,
{ name: string; paramKey?: string }
> = {
// note that similar lists are in src/interfaces/deepLinks.ts
claim: { name: "claim" },
"claim-add-raw": { name: "claim-add-raw" },
"claim-cert": { name: "claim-cert" },
"confirm-gift": { name: "confirm-gift" },
"contact-import": { name: "contact-import", paramKey: "jwt" },
did: { name: "did", paramKey: "did" },
"invite-one-accept": { name: "invite-one-accept", paramKey: "jwt" },
"onboard-meeting-members": { name: "onboard-meeting-members" },
project: { name: "project" },
"user-profile": { name: "user-profile" },
};
/**
* Parses deep link URL into path, params and query components.
* Validates URL structure using Zod schemas.
*
@@ -115,18 +119,9 @@ export class DeepLinkHandler {
const [path, queryString] = parts[1].split("?");
const [routePath, ...pathParams] = path.split("/");
// logger.info(
// "[DeepLink] Debug:",
// "Route Path:",
// routePath,
// "Path Params:",
// pathParams,
// "Query String:",
// queryString,
// );
// Validate route exists before proceeding
if (!this.ROUTE_MAP[routePath]) {
if (!ROUTE_MAP[routePath]) {
throw {
code: "INVALID_ROUTE",
message: `Invalid route path: ${routePath}`,
@@ -144,9 +139,14 @@ export class DeepLinkHandler {
const params: Record<string, string> = {};
if (pathParams) {
// Now we know routePath exists in ROUTE_MAP
const routeConfig = this.ROUTE_MAP[routePath];
const routeConfig = ROUTE_MAP[routePath];
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
}
// logConsoleAndDb(
// `[DeepLink] Debug: Route Path: ${routePath} Path Params: ${JSON.stringify(params)} Query String: ${JSON.stringify(query)}`,
// false,
// );
return { path: routePath, params, query };
}
@@ -170,7 +170,7 @@ export class DeepLinkHandler {
try {
// Validate route exists
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
routeName = this.ROUTE_MAP[validRoute].name;
routeName = ROUTE_MAP[validRoute].name;
} catch (error) {
// Log the invalid route attempt
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
@@ -178,52 +178,65 @@ export class DeepLinkHandler {
// Redirect to error page with information about the invalid link
await this.router.replace({
name: "deep-link-error",
params,
query: {
originalPath: path,
errorCode: "INVALID_ROUTE",
message: `The link you followed (${path}) is not supported`,
errorMessage: `The link you followed (${path}) is not supported`,
...query,
},
});
throw {
code: "INVALID_ROUTE",
message: `Unsupported route: ${path}`,
};
// This previously threw an error but we're redirecting so there's no need.
return;
}
// Continue with parameter validation as before...
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
let validatedParams, validatedQuery;
try {
const validatedParams = await schema.parseAsync({
...params,
...query,
});
await this.router.replace({
name: routeName,
params: validatedParams,
query,
});
validatedParams = await schema.parseAsync(params);
validatedQuery = await schema.parseAsync(query);
} catch (error) {
// For parameter validation errors, provide specific error feedback
logConsoleAndDb(`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`, true);
await this.router.replace({
name: "deep-link-error",
params,
query: {
originalPath: path,
errorCode: "INVALID_PARAMETERS",
message: `The link parameters are invalid: ${(error as Error).message}`,
errorMessage: `The link parameters are invalid: ${(error as Error).message}`,
...query,
},
});
throw {
code: "INVALID_PARAMETERS",
message: (error as Error).message,
details: error,
params: params,
query: query,
};
// This previously threw an error but we're redirecting so there's no need.
return;
}
try {
await this.router.replace({
name: routeName,
params: validatedParams,
query: validatedQuery,
});
} catch (error) {
logConsoleAndDb(`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)} ... and validated query: ${JSON.stringify(validatedQuery)}`, true);
// For parameter validation errors, provide specific error feedback
await this.router.replace({
name: "deep-link-error",
params: validatedParams,
query: {
originalPath: path,
errorCode: "ROUTING_ERROR",
errorMessage: `Error routing to ${routeName}: ${(JSON.stringify(error))}`,
...validatedQuery,
},
});
}
}
/**
@@ -235,7 +248,6 @@ export class DeepLinkHandler {
*/
async handleDeepLink(url: string): Promise<void> {
try {
logConsoleAndDb("[DeepLink] Processing URL: " + url, false);
const { path, params, query } = this.parseDeepLink(url);
// Ensure params is always a Record<string,string> by converting undefined to empty string
const sanitizedParams = Object.fromEntries(
@@ -245,7 +257,7 @@ export class DeepLinkHandler {
} catch (error) {
const deepLinkError = error as DeepLinkError;
logConsoleAndDb(
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`,
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
true,
);

View File

@@ -31,7 +31,7 @@
<h2>Supported Deep Links</h2>
<ul>
<li v-for="(routeItem, index) in validRoutes" :key="index">
<code>timesafari://{{ routeItem }}/:id</code>
<code>timesafari://{{ routeItem }}/:{{ deepLinkSchemaKeys[routeItem] }}</code>
</li>
</ul>
</div>
@@ -41,12 +41,19 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { VALID_DEEP_LINK_ROUTES } from "../interfaces/deepLinks";
import { VALID_DEEP_LINK_ROUTES, deepLinkSchemas } from "../interfaces/deepLinks";
import { logConsoleAndDb } from "../db/databaseUtil";
import { logger } from "../utils/logger";
const route = useRoute();
const router = useRouter();
// an object with the route as the key and the first param name as the value
const deepLinkSchemaKeys = Object.fromEntries(
Object.entries(deepLinkSchemas).map(([route, schema]) => {
const param = Object.keys(schema.shape)[0];
return [route, param];
})
);
// Extract error information from query params
const errorCode = computed(
@@ -54,7 +61,7 @@ const errorCode = computed(
);
const errorMessage = computed(
() =>
(route.query.message as string) ||
(route.query.errorMessage as string) ||
"The deep link you followed is invalid or not supported.",
);
const originalPath = computed(() => route.query.originalPath as string);
@@ -93,7 +100,7 @@ const reportIssue = () => {
// Log the error for analytics
onMounted(() => {
logConsoleAndDb(
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`,
`[DeepLinkError] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`,
true,
);
});

View File

@@ -128,7 +128,7 @@ export default class InviteOneAcceptView extends Vue {
}
// Extract JWT from route path
const jwt = (this.$route.params.jwt as string) || "";
const jwt = (this.$route.params.jwt as string) || this.$route.query.jwt as string || "";
await this.processInvite(jwt, false);
this.checkingInvite = false;

View File

@@ -110,7 +110,7 @@ test('Record something given', async ({ page }) => {
// Refresh home view and check gift
await page.goto('./');
const item = await page.locator('li').filter({ hasText: finalTitle });
await item.getByTestId('circle-info-link').first().click();
await item.locator('[data-testid="circle-info-link"]').click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
const page1Promise = page.waitForEvent('popup');

View File

@@ -90,7 +90,7 @@ test('Record item given from image-share', async ({ page }) => {
// Refresh home view and check gift
await page.goto('./');
const item1 = page.locator('li').filter({ hasText: finalTitle }).first();
const item1 = page.locator('li').filter({ hasText: finalTitle });
await expect(item1.getByRole('img')).toBeVisible();
});

View File

@@ -102,7 +102,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await expect(page.locator('li.border-b')).toContainText(userName);
// Rename contact
await page.getByTestId(`contactListItem`).locator(`h2 a:has-text("${userName}")`).click();
await page.locator(`li[data-testid="contactListItem"] h2:has-text("${userName}") + span svg.fa-circle-info`).click();
// now on the DID view page
await page.locator('h2 svg.fa-pen').click();
// now on the contact edit page
@@ -116,11 +116,10 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
// Confirm that home shows contact in "Record Something…"
await page.goto('./');
await page.getByTestId('closeOnboardingAndFinish').click();
const userGaveLink = page.locator('#sectionRecordSomethingGiven ul li').filter({ hasText: contactName }).nth(0);
await expect(userGaveLink).toBeVisible();
await userGaveLink.click();
await expect(page.locator('#sectionRecordSomethingGiven ul li').filter({ hasText: contactName }).nth(0)).toBeVisible();
// Record something given by new contact
await page.getByRole('heading', { name: contactName }).click();
await page.getByPlaceholder('What was given').fill(finalTitle);
await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString());
await page.getByRole('button', { name: 'Sign & Send' }).click();
@@ -131,7 +130,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
// Firefox complains on load the initial feed here when we use the test server.
// It may be similar to the CORS problem below.
await page.locator('li').filter({ hasText: finalTitle }).getByTestId('circle-info-link').click();
await page.locator('li').filter({ hasText: finalTitle }).locator('a').click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
@@ -155,7 +154,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();
const giftLink = page.locator('li').filter({ hasText: finalTitle }).getByTestId('circle-info-link').first();
const giftLink = page.locator('li').filter({ hasText: finalTitle }).locator('a');
await expect(giftLink).toBeVisible();
await giftLink.click();
@@ -227,7 +226,7 @@ test('Add contact, copy details, delete, and import from paste & from file', asy
// See a different clipboard solution below.
// see contact details on the second contact
await page.getByTestId('contactListItem').nth(1).locator(`h2 a)`).click();
await page.getByTestId('contactListItem').nth(1).locator('a').click();
await page.getByRole('heading', { name: 'Identifier Details' }).isVisible();
// remove contact
await page.locator('button > svg.fa-trash-can').click();

View File

@@ -63,7 +63,7 @@ export async function deleteContact(page: Page, did: string): Promise<void> {
await page.goto('./contacts');
const contactName = createContactName(did);
// go to the detail page for this contact
await page.getByTestId(`contactListItem`).locator(`h2 a:has-text("${contactName}")`).click();
await page.locator(`li[data-testid="contactListItem"] h2:has-text("${contactName}") + span svg.fa-circle-info`).click();
// delete the contact
await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click();
@@ -82,7 +82,7 @@ export async function generateNewEthrUser(page: Page): Promise<string> {
return newDid;
}
// Generate a new random user, register them, and add them as a contact with name from createContactName.
// Generate a new random user and register them.
// Note that this makes 000 the active user. Use switchToUser to switch to this DID.
export async function generateAndRegisterEthrUser(page: Page): Promise<string> {
const newDid = await generateNewEthrUser(page);
@@ -109,7 +109,7 @@ export async function generateRandomString(length: number): Promise<string> {
// Function to create an array of unique strings
export async function createUniqueStringsArray(count: number): Promise<string[]> {
const stringsArray: string[] = [];
const stringsArray = [];
const stringLength = 16;
for (let i = 0; i < count; i++) {
@@ -122,7 +122,7 @@ export async function createUniqueStringsArray(count: number): Promise<string[]>
// Function to create an array of two-digit non-zero numbers
export async function createRandomNumbersArray(count: number): Promise<number[]> {
const numbersArray: number[] = [];
const numbersArray = [];
for (let i = 0; i < count; i++) {
let randomNumber = Math.floor(Math.random() * 99) + 1;

View File

@@ -6,7 +6,9 @@ import path from "path";
import { fileURLToPath } from 'url';
// Load environment variables
dotenv.config();
console.log('NODE_ENV:', process.env.NODE_ENV)
dotenv.config({ path: `.env.${process.env.NODE_ENV}` })
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);