Browse Source

feat(accessibility): enhance AccountViewView and document test suite

- Add ARIA annotations and roles to AccountViewView for better screen reader support
  - Add role="tooltip" to API server description
  - Improve input control accessibility with proper ARIA attributes
  - Add descriptive labels and aria-labels for interactive elements

- Create comprehensive README.md for Playwright test suite
  - Document test structure and organization
  - Add setup instructions and prerequisites
  - Include troubleshooting guide and contribution guidelines
  - Link to related documentation

This change improves accessibility compliance and makes the test suite
more maintainable for contributors.
pull/133/head
Matthew Raymer 3 weeks ago
parent
commit
ea108b754e
  1. 2962
      package-lock.json
  2. 120
      src/views/AccountViewView.vue
  3. 2
      test-playwright/00-noid-tests.spec.ts
  4. 119
      test-playwright/README.md

2962
package-lock.json

File diff suppressed because it is too large

120
src/views/AccountViewView.vue

@ -3,7 +3,7 @@
<TopMessage /> <TopMessage />
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> <main id="Content" class="p-6 pb-24 max-w-3xl mx-auto" role="main" aria-label="Account Profile">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light"> <h1 id="ViewHeading" class="text-4xl text-center font-light">
Your Identity Your Identity
@ -14,6 +14,8 @@
v-if="!activeDid" v-if="!activeDid"
id="noticeBeforeShare" id="noticeBeforeShare"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
role="alert"
aria-live="polite"
> >
<p class="mb-4"> <p class="mb-4">
<b>Note:</b> Before you can share with others or take any action, you <b>Note:</b> Before you can share with others or take any action, you
@ -28,10 +30,12 @@
</div> </div>
<!-- Identity Details --> <!-- Identity Details -->
<div <section
id="sectionIdentityDetails" id="sectionIdentityDetails"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4" class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"
aria-labelledby="identityDetailsHeading"
> >
<h2 id="identityDetailsHeading" class="sr-only">Identity Details</h2>
<div v-if="givenName"> <div v-if="givenName">
<h2 class="text-xl font-semibold mb-2"> <h2 class="text-xl font-semibold mb-2">
<span class="whitespace-nowrap"> <span class="whitespace-nowrap">
@ -75,11 +79,17 @@
:profile-image-url="profileImageUrl" :profile-image-url="profileImageUrl"
class="inline-block align-text-bottom border border-slate-300 rounded" class="inline-block align-text-bottom border border-slate-300 rounded"
@click="showLargeIdenticonUrl = profileImageUrl" @click="showLargeIdenticonUrl = profileImageUrl"
role="button"
aria-label="View profile image in large size"
tabindex="0"
/> />
<font-awesome <font-awesome
icon="trash-can" icon="trash-can"
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12" class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
@click="confirmDeleteImage" @click="confirmDeleteImage"
role="button"
aria-label="Delete profile image"
tabindex="0"
/> />
</span> </span>
<div v-else class="text-center"> <div v-else class="text-center">
@ -140,17 +150,20 @@
<div <div
class="text-sm text-slate-500 flex justify-start items-center mb-1" class="text-sm text-slate-500 flex justify-start items-center mb-1"
data-testId="didWrapper" data-testId="didWrapper"
role="region"
aria-label="Your Identifier"
> >
<code class="truncate">{{ activeDid }}</code> <code class="truncate" aria-label="Your DID">{{ activeDid }}</code>
<button <button
class="ml-2" class="ml-2"
@click=" @click="
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy)) doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
" "
aria-label="Copy DID to clipboard"
> >
<font-awesome icon="copy" class="text-slate-400 fa-fw"></font-awesome> <font-awesome icon="copy" class="text-slate-400 fa-fw" aria-hidden="true"></font-awesome>
</button> </button>
<span v-show="showDidCopy">Copied</span> <span v-show="showDidCopy" role="status" aria-live="polite">Copied</span>
</div> </div>
<div class="text-blue-500 text-sm font-bold"> <div class="text-blue-500 text-sm font-bold">
@ -158,7 +171,7 @@
Your Activity Your Activity
</router-link> </router-link>
</div> </div>
</div> </section>
<!-- Registration notice --> <!-- Registration notice -->
<!-- <!--
@ -169,6 +182,8 @@
v-if="!isRegistered" v-if="!isRegistered"
id="noticeBeforeAnnounce" id="noticeBeforeAnnounce"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
role="alert"
aria-live="polite"
> >
<p class="mb-4"> <p class="mb-4">
<b>Note:</b> Before you can publicly announce a new project or time <b>Note:</b> Before you can publicly announce a new project or time
@ -182,27 +197,31 @@
</router-link> </router-link>
</div> </div>
<div <section
v-if="isRegistered" v-if="isRegistered"
id="sectionNotifications" id="sectionNotifications"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
aria-labelledby="notificationsHeading"
> >
<!-- label --> <h2 id="notificationsHeading" class="mb-2 font-bold">Notifications</h2>
<div class="mb-2 font-bold">Notifications</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<!-- label -->
<div> <div>
Reminder Notification Reminder Notification
<font-awesome <button
icon="question-circle"
class="text-slate-400 fa-fw ml-2 cursor-pointer" class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click.stop="showReminderNotificationInfo" @click.stop="showReminderNotificationInfo"
/> aria-label="Learn more about reminder notifications"
>
<font-awesome icon="question-circle" aria-hidden="true"></font-awesome>
</button>
</div> </div>
<!-- toggle -->
<div <div
class="relative ml-2 cursor-pointer" class="relative ml-2 cursor-pointer"
@click="showReminderNotificationChoice()" @click="showReminderNotificationChoice()"
role="switch"
:aria-checked="notifyingReminder"
aria-label="Toggle reminder notifications"
tabindex="0"
> >
<!-- input --> <!-- input -->
<input v-model="notifyingReminder" type="checkbox" class="sr-only" /> <input v-model="notifyingReminder" type="checkbox" class="sr-only" />
@ -253,49 +272,47 @@
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications"> <router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
Troubleshoot your notifications. Troubleshoot your notifications.
</router-link> </router-link>
</div> </section>
<PushNotificationPermission ref="pushNotificationPermission" /> <PushNotificationPermission ref="pushNotificationPermission" />
<div <section
id="sectionSearchLocation" id="sectionSearchLocation"
class="flex justify-between bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" class="flex justify-between bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
aria-labelledby="searchLocationHeading"
> >
<!-- label --> <h2 id="searchLocationHeading" class="mb-2 font-bold">Location for Searches</h2>
<span class="mb-2 font-bold">Location for Searches</span>
<router-link <router-link
:to="{ name: 'search-area' }" :to="{ name: 'search-area' }"
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2" class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
> >
{{ isSearchAreasSet ? "Change" : "Set" }} Search Area {{ isSearchAreasSet ? "Change" : "Set" }} Search Area
</router-link> </router-link>
</div> </section>
<!-- User Profile --> <!-- User Profile -->
<div <section
v-if="isRegistered" v-if="isRegistered"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
aria-labelledby="userProfileHeading"
> >
<div v-if="loadingProfile" class="text-center mb-2"> <h2 id="userProfileHeading" class="flex items-center mb-2">
<font-awesome
icon="spinner"
class="fa-spin text-slate-400"
></font-awesome>
Loading profile...
</div>
<div v-else class="flex items-center mb-2">
<span class="font-bold">Public Profile</span> <span class="font-bold">Public Profile</span>
<font-awesome <button
icon="circle-info"
class="text-slate-400 fa-fw ml-2 cursor-pointer" class="text-slate-400 fa-fw ml-2 cursor-pointer"
@click="showProfileInfo" @click="showProfileInfo"
/> aria-label="Learn more about public profile"
</div> >
<font-awesome icon="circle-info" aria-hidden="true"></font-awesome>
</button>
</h2>
<textarea <textarea
v-model="userProfileDesc" v-model="userProfileDesc"
class="w-full h-32 p-2 border border-slate-300 rounded-md" class="w-full h-32 p-2 border border-slate-300 rounded-md"
placeholder="Write something about yourself for the public..." placeholder="Write something about yourself for the public..."
:readonly="loadingProfile || savingProfile" :readonly="loadingProfile || savingProfile"
:class="{ 'bg-slate-100': loadingProfile || savingProfile }" :class="{ 'bg-slate-100': loadingProfile || savingProfile }"
aria-label="Public profile description"
:aria-busy="loadingProfile || savingProfile"
></textarea> ></textarea>
<div class="flex items-center mb-4" @click="toggleUserProfileLocation"> <div class="flex items-center mb-4" @click="toggleUserProfileLocation">
@ -364,18 +381,19 @@
</div> </div>
<div v-else-if="loadingProfile">Loading...</div> <div v-else-if="loadingProfile">Loading...</div>
<div v-else>Saving...</div> <div v-else>Saving...</div>
</div> </section>
<div <section
v-if="activeDid" v-if="activeDid"
id="sectionUsageLimits" id="sectionUsageLimits"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
aria-labelledby="usageLimitsHeading"
> >
<div class="mb-2 font-bold">Usage Limits</div> <h2 id="usageLimitsHeading" class="mb-2 font-bold">Usage Limits</h2>
<!-- show spinner if loading limits --> <!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="text-center"> <div v-if="loadingLimits" class="text-center" role="status" aria-live="polite">
Checking&hellip; Checking&hellip;
<font-awesome icon="spinner" class="fa-spin"></font-awesome> <font-awesome icon="spinner" class="fa-spin" aria-hidden="true"></font-awesome>
</div> </div>
<div class="mb-4 text-center"> <div class="mb-4 text-center">
{{ limitsMessage }} {{ limitsMessage }}
@ -423,7 +441,7 @@
> >
Recheck Limits Recheck Limits
</button> </button>
</div> </section>
<DataExportSection :active-did="activeDid" /> <DataExportSection :active-did="activeDid" />
@ -435,7 +453,8 @@
> >
Advanced Advanced
</h3> </h3>
<div v-if="showAdvanced || showGeneralAdvanced" id="sectionAdvanced"> <section v-if="showAdvanced || showGeneralAdvanced" id="sectionAdvanced" aria-labelledby="advancedHeading">
<h2 id="advancedHeading" class="text-blue-500 text-sm font-semibold mb-3">Advanced</h2>
<p class="text-rose-600 mb-8"> <p class="text-rose-600 mb-8">
Beware: the features here can be confusing and even change data in ways Beware: the features here can be confusing and even change data in ways
you do not expect. But we support your freedom! you do not expect. But we support your freedom!
@ -606,42 +625,61 @@
<div id="sectionClaimServer"> <div id="sectionClaimServer">
<h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2> <h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
<div class="px-4 py-4"> <div class="px-4 py-4" role="group" aria-labelledby="claimServerHeading">
<h3 id="claimServerHeading" class="sr-only">Claim Server Configuration</h3>
<label for="apiServerInput" class="sr-only">API Server URL</label>
<input <input
id="apiServerInput"
v-model="apiServerInput" v-model="apiServerInput"
type="text" type="text"
class="block w-full rounded border border-slate-400 px-4 py-2" class="block w-full rounded border border-slate-400 px-4 py-2"
aria-describedby="apiServerDescription"
placeholder="Enter API server URL"
/> />
<div
id="apiServerDescription"
class="sr-only"
role="tooltip"
>
Enter the URL for the claim server. You can use the buttons below to quickly set common server URLs.
</div>
<button <button
v-if="apiServerInput != apiServer" v-if="apiServerInput != apiServer"
class="w-full px-4 rounded bg-yellow-500 border border-slate-400" class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
@click="onClickSaveApiServer()" @click="onClickSaveApiServer()"
aria-label="Save API server URL"
> >
<font-awesome <font-awesome
icon="floppy-disk" icon="floppy-disk"
class="fa-fw" class="fa-fw"
color="white" color="white"
aria-hidden="true"
></font-awesome> ></font-awesome>
</button> </button>
<div class="mt-2" role="group" aria-label="Quick server selection">
<button <button
class="px-3 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="apiServerInput = AppConstants.PROD_ENDORSER_API_SERVER" @click="apiServerInput = AppConstants.PROD_ENDORSER_API_SERVER"
aria-label="Use production server URL"
> >
Use Prod Use Prod
</button> </button>
<button <button
class="px-3 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="apiServerInput = AppConstants.TEST_ENDORSER_API_SERVER" @click="apiServerInput = AppConstants.TEST_ENDORSER_API_SERVER"
aria-label="Use test server URL"
> >
Use Test Use Test
</button> </button>
<button <button
class="px-3 rounded bg-slate-200 border border-slate-400" class="px-3 rounded bg-slate-200 border border-slate-400"
@click="apiServerInput = AppConstants.LOCAL_ENDORSER_API_SERVER" @click="apiServerInput = AppConstants.LOCAL_ENDORSER_API_SERVER"
aria-label="Use local server URL"
> >
Use Local Use Local
</button> </button>
</div> </div>
</div>
<label <label
for="toggleProdWarningMessage" for="toggleProdWarningMessage"
@ -878,8 +916,8 @@
> >
View Logs View Logs
</router-link> </router-link>
</div>
</section> </section>
</main>
</template> </template>
<script lang="ts"> <script lang="ts">

2
test-playwright/00-noid-tests.spec.ts

@ -179,7 +179,7 @@ test('Confirm test API setting (may fail if you are running your own Time Safari
const endorserTermInConfig = endorserTerm?.substring(ENDORSER_ENV_NAME.length + 1); const endorserTermInConfig = endorserTerm?.substring(ENDORSER_ENV_NAME.length + 1);
const endorserServer = endorserTermInConfig || 'https://test-api.endorser.ch'; const endorserServer = endorserTermInConfig || 'https://test-api.endorser.ch';
await expect(page.getByRole('textbox').nth(1)).toHaveValue(endorserServer); await expect(page.locator('#apiServerInput')).toHaveValue(endorserServer);
}); });
test('Check User 0 can register a random person', async ({ page }) => { test('Check User 0 can register a random person', async ({ page }) => {

119
test-playwright/README.md

@ -0,0 +1,119 @@
# Playwright Test Suite
This directory contains the automated end-to-end test suite for Time Safari using Playwright. The tests verify critical functionality across web and mobile platforms.
## Test Structure
Tests are organized by feature area and numbered for execution order:
- `00-noid-tests.spec.ts` - Tests for unregistered users
- `05-invite.spec.ts` - Contact invitation functionality
- `10-check-usage-limits.spec.ts` - Usage limit verification
- `20-create-project.spec.ts` - Project creation
- `25-create-project-x10.spec.ts` - Bulk project creation
- `30-record-gift.spec.ts` - Gift recording
- `33-record-gift-x10.spec.ts` - Bulk gift recording
- `35-record-gift-from-image-share.spec.ts` - Gift recording from shared images
- `37-record-gift-on-project.spec.ts` - Project-specific gift recording
- `40-add-contact.spec.ts` - Contact management
- `50-record-offer.spec.ts` - Offer recording
- `60-new-activity.spec.ts` - Activity feed updates
## Key Files
- `testUtils.ts` - Shared test utilities and helper functions
- `TESTING.md` - Detailed testing guide and manual test procedures
- `playwright.config-local.ts` - Playwright configuration for local testing
- `exported-data.json` - Test data for import/export testing
## Prerequisites
1. Endorser server running locally (see TESTING.md for setup)
2. Playwright browsers installed:
```bash
npx playwright install
```
3. For mobile testing:
- XCode (for iOS)
- Android Studio or connected Android device
## Running Tests
### Full Test Suite
```bash
# Run all tests (web + mobile)
npm run test:all
# Run web-only tests
npm run test:web
```
### Individual Tests
```bash
# Run a specific test with tracing
npx playwright test -c playwright.config-local.ts --trace on test-playwright/40-add-contact.spec.ts
```
### Test Environment Options
1. Local Endorser Server (default):
```bash
NODE_ENV=test-local npm run dev
```
2. Global Test Server:
```bash
VITE_DEFAULT_ENDORSER_API_SERVER=https://test-ledger.time.com npm run dev
```
3. Minimal Test Data:
```bash
rm ../endorser-ch-test-local.sqlite3
NODE_ENV=test-local npm run flyway migrate
NODE_ENV=test-local npm run test test/controller0
NODE_ENV=test-local npm run dev
```
## Test Data
The test suite uses predefined test users, with User #0 having registration privileges:
- DID: `did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F`
- Seed phrase available in TESTING.md
## Troubleshooting
Common issues and solutions:
1. **Test Failures**
- Some tests may fail intermittently - try rerunning
- Check Endorser server logs for backend issues
- Verify test environment setup
2. **Mobile Testing**
- Ensure XCode/Android Studio is running
- Check device connectivity
- Verify browser installation
3. **Data Issues**
- Clear browser data if tests fail due to stale state
- Reset IndexedDB if needed
- Check service worker status
For more detailed troubleshooting, see TESTING.md.
## Contributing
When adding new tests:
1. Follow the existing naming convention
2. Use testUtils.ts for common operations
3. Add appropriate comments and documentation
4. Update this README if adding new test categories
5. Consider both web and mobile platforms
## Related Documentation
- [TESTING.md](./TESTING.md) - Detailed testing guide
- [Playwright Documentation](https://playwright.dev/docs/intro)
- Endorser server documentation for test setup
Loading…
Cancel
Save