Compare commits

..

2 Commits

Author SHA1 Message Date
Cursor Agent
9af9dc84e9 feat: Trigger CI on VRT update and checkout PR branch
Co-authored-by: matiss <matiss@mja.lv>
2025-10-18 17:30:19 +00:00
Cursor Agent
57710006e0 Add CI checks to VRT update workflow
Co-authored-by: matiss <matiss@mja.lv>
2025-10-18 17:26:22 +00:00
224 changed files with 1613 additions and 8564 deletions

View File

@@ -1,3 +0,0 @@
{
"setup-worktree": ["yarn"]
}

View File

@@ -17,7 +17,7 @@ runs:
- name: Install node
uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
- name: Install yarn
run: npm install -g yarn
shell: bash
@@ -32,16 +32,6 @@ runs:
with:
path: ${{ format('{0}/**/node_modules', inputs.working-directory) }}
key: yarn-v1-${{ runner.os }}-${{ steps.get-node.outputs.version }}-${{ hashFiles(format('{0}/**/yarn.lock', inputs.working-directory)) }}
- name: Ensure Lage cache directory exists
run: mkdir -p ${{ format('{0}/.lage', inputs.working-directory) }}
shell: bash
- name: Cache Lage
uses: actions/cache@v4
with:
path: ${{ format('{0}/.lage', inputs.working-directory) }}
key: lage-${{ runner.os }}-${{ github.sha }}
restore-keys: |
lage-${{ runner.os }}-
- name: Install
working-directory: ${{ inputs.working-directory }}
run: yarn --immutable

View File

@@ -12,6 +12,8 @@ on:
branches:
- master
pull_request:
repository_dispatch:
types: [vrt-update-applied]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -22,10 +24,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build API
run: cd packages/api && yarn build
- name: Create package tgz
@@ -40,10 +44,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build CRDT
run: cd packages/crdt && yarn build
- name: Create package tgz
@@ -58,6 +64,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Web
@@ -77,10 +87,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build Server
run: yarn workspace @actual-app/sync-server build
- name: Upload Build

View File

@@ -5,6 +5,8 @@ on:
branches:
- master
pull_request:
repository_dispatch:
types: [vrt-update-applied]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -15,30 +17,36 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Lint
run: yarn lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Typecheck
run: yarn typecheck
validate-cli:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Build Web
run: yarn build:server
- name: Check that the built CLI works
@@ -47,20 +55,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- name: Set up environment
uses: ./.github/actions/setup
with:
download-translations: 'false'
- name: Test
run: yarn test
migrations:
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' || github.event_name == 'repository_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# For repository_dispatch events, checkout the PR branch
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_ref || github.ref }}
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.head_repo || github.repository }}
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
- name: Check migrations
run: node ./.github/actions/check-migrations.js

View File

@@ -37,8 +37,6 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Run E2E Tests on Netlify URL
run: yarn e2e
env:
@@ -60,8 +58,6 @@ jobs:
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Trust the repository directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Run Desktop app E2E Tests
run: |
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- yarn e2e:desktop

View File

@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
- name: Handle feature requests
run: node .github/actions/handle-feature-requests.js
env:

View File

@@ -73,7 +73,7 @@ jobs:
- name: Setup node and npm registry
uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Publish Web

View File

@@ -56,7 +56,7 @@ jobs:
- name: Setup node and npm registry
uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Publish Web

View File

@@ -130,6 +130,30 @@ jobs:
git push origin "HEAD:refs/heads/$HEAD_REF"
echo "Successfully pushed VRT updates to $HEAD_REPO@$HEAD_REF"
- name: Trigger CI workflows
if: steps.apply.outputs.applied == 'true'
uses: actions/github-script@v7
with:
script: |
// Dispatch a custom event to trigger CI workflows
// This will cause the CI workflows to run in the PR context
try {
await github.rest.repos.createDispatchEvent({
owner: context.repo.owner,
repo: context.repo.repo,
event_type: 'vrt-update-applied',
client_payload: {
pr_number: ${{ steps.metadata.outputs.pr_number }},
head_ref: '${{ steps.metadata.outputs.head_ref }}',
head_repo: '${{ steps.metadata.outputs.head_repo }}'
}
});
console.log('Successfully triggered CI workflows via repository_dispatch');
} catch (error) {
console.log(`Failed to trigger CI workflows: ${error.message}`);
}
- name: Comment on PR - Success
if: steps.apply.outputs.applied == 'true'
uses: actions/github-script@v7
@@ -139,7 +163,7 @@ jobs:
issue_number: ${{ steps.metadata.outputs.pr_number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: '✅ VRT screenshots have been automatically updated.'
body: '✅ VRT screenshots have been automatically updated and CI workflows have been triggered.'
});
- name: Comment on PR - Failure

3
.gitignore vendored
View File

@@ -62,6 +62,3 @@ build/
# .d.ts files aren't type-checked with skipLibCheck set to true
*.d.ts
# Lage cache
.lage/

2
.nvmrc
View File

@@ -1 +1 @@
v22/*
v20/*

View File

@@ -40,28 +40,7 @@ yarn start:desktop
- **ALWAYS run yarn commands from the root directory** - never run them in child workspaces
- Use `yarn workspace <workspace-name> run <command>` for workspace-specific tasks
- Tests run once and exit by default (using `vitest --run`)
### Task Orchestration with Lage
The project uses **[lage](https://microsoft.github.io/lage/)** (a task runner for JavaScript monorepos) to efficiently run tests and other tasks across multiple workspaces:
- **Parallel execution**: Runs tests in parallel across workspaces for faster feedback
- **Smart caching**: Caches test results to skip unchanged packages (cached in `.lage/` directory)
- **Dependency awareness**: Understands workspace dependencies and execution order
- **Continues on error**: Uses `--continue` flag to run all packages even if one fails
**Lage Commands:**
```bash
# Run all tests across all packages
yarn test # Equivalent to: lage test --continue
# Run tests without cache (for debugging/CI)
yarn test:debug # Equivalent to: lage test --no-cache --continue
```
Configuration is in `lage.config.js` at the project root.
- Include `--watch=false` flag when running unit tests to prevent watch mode
## Architecture & Package Structure
@@ -75,13 +54,8 @@ The core application logic that runs on any platform.
- Platform-agnostic code
- Exports for both browser and node environments
- Test commands:
```bash
# Run all loot-core tests
yarn workspace loot-core run test
# Or run tests across all packages using lage
yarn test
yarn workspace loot-core run test --watch=false
```
#### 2. **desktop-client** (`packages/desktop-client/` - aliased as `@actual-app/web`)
@@ -121,16 +95,9 @@ Public API for programmatic access to Actual.
- Node.js API
- Designed for integrations and automation
- Commands:
```bash
# Build
yarn workspace @actual-app/api build
# Run tests
yarn workspace @actual-app/api test
# Or use lage to run all tests
yarn test
yarn workspace @actual-app/api test --watch=false
```
#### 5. **sync-server** (`packages/sync-server/` - aliased as `@actual-app/sync-server`)
@@ -190,29 +157,24 @@ When implementing changes:
**Unit Tests (Vitest)**
The project uses **lage** for running tests across all workspaces efficiently.
```bash
# Run all tests across all packages (using lage)
# All tests
yarn test
# Run tests without cache (for debugging)
yarn test:debug
# Specific package
yarn workspace loot-core run test --watch=false
# Run tests for a specific package
yarn workspace loot-core run test
# Run a specific test file (watch mode)
yarn workspace loot-core run test path/to/test.test.ts
# Specific test file
yarn workspace loot-core run test path/to/test.test.ts --watch=false
```
**E2E Tests (Playwright)**
```bash
# Run E2E tests for web
yarn e2e
# Desktop client E2E
yarn workspace @actual-app/web e2e
# Desktop Electron E2E (includes full build)
# Desktop Electron E2E
yarn e2e:desktop
# Visual regression tests
@@ -220,9 +182,6 @@ yarn vrt
# Visual regression in Docker (consistent environment)
yarn vrt:docker
# Run E2E tests for a specific package
yarn workspace @actual-app/web e2e
```
**Testing Best Practices:**
@@ -370,7 +329,6 @@ describe('ComponentName', () => {
### Configuration Files
- `/package.json` - Root workspace configuration, scripts
- `/lage.config.js` - Lage task runner configuration
- `/eslint.config.mjs` - ESLint configuration (flat config format)
- `/tsconfig.json` - Root TypeScript configuration
- `/.cursorignore`, `/.gitignore` - Ignored files
@@ -390,7 +348,6 @@ describe('ComponentName', () => {
- `packages/*/build/` - Built output
- `packages/desktop-client/playwright-report/` - Test reports
- `packages/desktop-client/test-results/` - Test results
- `.lage/` - Lage task runner cache (improves test performance)
### Key Source Directories
@@ -409,11 +366,8 @@ describe('ComponentName', () => {
### Running Specific Tests
```bash
# Run all tests across all packages (recommended)
yarn test
# Unit test for a specific file in loot-core (watch mode)
yarn workspace loot-core run test src/path/to/file.test.ts
# Unit test for a specific file in loot-core
yarn workspace loot-core run test src/path/to/file.test.ts --watch=false
# E2E test for a specific file
yarn workspace @actual-app/web run playwright test accounts.test.ts --browser=chromium
@@ -478,8 +432,6 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
2. For Vitest: check `vitest.config.ts` or `vitest.web.config.ts`
3. For Playwright: check `playwright.config.ts`
4. Ensure mock minimization - prefer real implementations
5. **Lage cache issues**: Clear cache with `rm -rf .lage` if tests behave unexpectedly
6. **Tests continue on error**: With `--continue` flag, all packages run even if one fails
### Import Resolution Issues
@@ -514,7 +466,8 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
### Visual Regression Tests (VRT)
- Snapshots stored per test file in `*-snapshots/` directories
- Run with `VRT=true` environment variable
- Snapshots stored per test file
- Use Docker for consistent environment: `yarn vrt:docker`
## Additional Resources

View File

@@ -5,7 +5,7 @@
# you are doing.
###################################################
FROM node:22-bookworm as dev
FROM node:20-bullseye as dev
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y openssl
WORKDIR /app
CMD ["sh", "./bin/docker-start"]

View File

@@ -1,6 +1,5 @@
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import pluginImport from 'eslint-plugin-import';
import pluginJSXA11y from 'eslint-plugin-jsx-a11y';
import pluginReact from 'eslint-plugin-react';
@@ -72,7 +71,7 @@ const confusingBrowserGlobals = [
'top',
];
export default defineConfig(
export default pluginTypescript.config(
{
ignores: [
'packages/api/app/bundle.api.js',

View File

@@ -1,30 +0,0 @@
/** @type {import('lage').ConfigOptions} */
module.exports = {
pipeline: {
test: {
type: 'npmScript',
options: {
outputGlob: [
'coverage/**',
'**/test-results/**',
'**/playwright-report/**',
],
},
},
build: {
type: 'npmScript',
cache: true,
options: {
outputGlob: ['lib-dist/**', 'dist/**', 'build/**'],
},
},
},
cacheOptions: {
cacheStorageConfig: {
provider: 'local',
outputGlob: ['lib-dist/**', 'dist/**', 'build/**'],
},
},
npmClient: 'yarn',
concurrency: 2,
};

View File

@@ -40,12 +40,12 @@
"build:api": "yarn workspace @actual-app/api build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"generate:release-notes": "ts-node ./bin/release-note-generator.ts",
"test": "lage test --continue",
"test:debug": "lage test --no-cache --continue",
"e2e": "yarn workspace @actual-app/web run e2e",
"test": "yarn workspaces foreach --all --parallel --verbose run test",
"test:debug": "yarn workspaces foreach --all --verbose run test",
"e2e": "yarn workspaces foreach --all --exclude desktop-electron --parallel --verbose run e2e",
"e2e:desktop": "yarn build:desktop --skip-exe-build && yarn workspace desktop-electron e2e",
"playwright": "yarn workspace @actual-app/web run playwright",
"vrt": "yarn workspace @actual-app/web run vrt",
"vrt": "yarn workspaces foreach --all --parallel --verbose run vrt",
"vrt:docker": "./bin/run-vrt",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -m ./packages/loot-core",
"rebuild-node": "yarn workspace loot-core rebuild",
@@ -58,7 +58,7 @@
},
"devDependencies": {
"@octokit/rest": "^22.0.0",
"@types/node": "^22.18.11",
"@types/node": "^22.18.8",
"@types/prompts": "^2.4.9",
"@typescript-eslint/parser": "^8.46.0",
"cross-env": "^10.1.0",
@@ -68,12 +68,11 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-hooks": "^6.1.1",
"eslint-plugin-typescript-paths": "^0.0.33",
"globals": "^16.4.0",
"html-to-image": "^1.11.13",
"husky": "^9.1.7",
"lage": "^2.14.14",
"lint-staged": "^16.2.3",
"minimatch": "^10.0.3",
"node-jq": "^6.3.1",
@@ -92,7 +91,7 @@
"socks": ">=2.8.3"
},
"engines": {
"node": ">=22",
"node": ">=20",
"yarn": "^4.9.1"
},
"lint-staged": {

View File

@@ -19,7 +19,7 @@
"build:migrations": "cp migrations/*.sql dist/migrations",
"build:default-db": "cp default-db.sqlite dist/",
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
"test": "yarn run build:app && yarn run build:crdt && vitest --run",
"test": "yarn run build:app && yarn run build:crdt && vitest",
"clean": "rm -rf dist @types"
},
"dependencies": {

View File

@@ -5,11 +5,5 @@ export default {
// print only console.error
return type === 'stderr';
},
poolOptions: {
threads: {
maxThreads: 2,
minThreads: 1,
},
},
},
};

View File

@@ -6,6 +6,6 @@
"vitest": "^3.2.4"
},
"scripts": {
"test": "vitest --run"
"test": "vitest"
}
}

View File

@@ -5,10 +5,5 @@ export default defineConfig({
globals: true,
include: ['src/**/*.test.(js|jsx|ts|tsx)'],
environment: 'node',
poolOptions: {
threads: {
singleThread: true,
},
},
},
});

View File

@@ -54,6 +54,6 @@
"scripts": {
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
"test": "npm-run-all -cp 'test:*'",
"test:web": "ENV=web vitest --run -c vitest.web.config.ts"
"test:web": "ENV=web vitest -c vitest.web.config.ts"
}
}

View File

@@ -21,12 +21,6 @@ export default defineConfig({
environment: 'jsdom',
globals: true,
include: ['src/**/*.web.test.(js|jsx|ts|tsx)'],
poolOptions: {
threads: {
maxThreads: 2,
minThreads: 1,
},
},
},
resolve: {
alias: [

View File

@@ -12,7 +12,7 @@
"build:node": "tsc --p tsconfig.dist.json",
"proto:generate": "./bin/generate-proto",
"build": "rm -rf dist && yarn run build:node",
"test": "vitest --run --globals"
"test": "vitest --globals"
},
"dependencies": {
"google-protobuf": "^3.21.4",

View File

@@ -9,6 +9,7 @@ rm -fr build
export IS_GENERIC_BROWSER=1
export REACT_APP_BACKEND_WORKER_HASH=`ls "$ROOT"/../public/kcab/kcab.worker.*.js | sed 's/.*kcab\.worker\.\(.*\)\.js/\1/'`
export REACT_APP_PLUGIN_SERVICE_WORKER_HASH=`ls "$ROOT"/../service-worker/plugin-sw.*.js | sed 's/.*plugin-sw\.\(.*\)\.js/\1/'`
yarn build

View File

@@ -6,5 +6,6 @@ cd "$ROOT/.."
export IS_GENERIC_BROWSER=1
export PORT=3001
export REACT_APP_BACKEND_WORKER_HASH="dev"
export REACT_APP_PLUGIN_SERVICE_WORKER_HASH="dev"
yarn start

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -123,14 +123,11 @@ test.describe('Accounts', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
if (screenshot) await expect(page).toMatchThemeScreenshots();
const importButton = accountPage.page.getByRole('button', {
name: /Import \d+ transactions/,
});
await importButton.waitFor({ state: 'visible' });
if (screenshot) await expect(page).toMatchThemeScreenshots();
await importButton.click();
await expect(importButton).not.toBeVisible();
@@ -149,14 +146,12 @@ test.describe('Accounts', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
await expect(page).toMatchThemeScreenshots();
const importButton = accountPage.page.getByRole('button', {
name: /Import \d+ transactions/,
});
await importButton.waitFor({ state: 'visible' });
await expect(page).toMatchThemeScreenshots();
await expect(importButton).toBeDisabled();
await expect(await importButton.innerText()).toMatch(
/Import 0 transactions/,

View File

@@ -1,64 +0,0 @@
import { type Page } from '@playwright/test';
import { expect, test } from './fixtures';
import { ConfigurationPage } from './page-models/configuration-page';
import { type MobileBankSyncPage } from './page-models/mobile-bank-sync-page';
import { MobileNavigation } from './page-models/mobile-navigation';
test.describe('Mobile Bank Sync', () => {
let page: Page;
let navigation: MobileNavigation;
let bankSyncPage: MobileBankSyncPage;
let configurationPage: ConfigurationPage;
test.beforeEach(async ({ browser }) => {
page = await browser.newPage();
navigation = new MobileNavigation(page);
configurationPage = new ConfigurationPage(page);
await page.setViewportSize({
width: 350,
height: 600,
});
await page.goto('/');
await configurationPage.createTestFile();
bankSyncPage = await navigation.goToBankSyncPage();
});
test.afterEach(async () => {
await page.close();
});
test('checks the page visuals', async () => {
await bankSyncPage.waitToLoad();
await expect(
page.getByRole('heading', { name: 'Bank Sync' }),
).toBeVisible();
await expect(bankSyncPage.searchBox).toBeVisible();
await expect(bankSyncPage.searchBox).toHaveAttribute(
'placeholder',
'Filter accounts…',
);
await expect(page).toMatchThemeScreenshots();
});
test('searches for accounts', async () => {
await bankSyncPage.searchFor('Checking');
await expect(bankSyncPage.searchBox).toHaveValue('Checking');
await expect(page).toMatchThemeScreenshots();
});
test('page handles empty state gracefully', async () => {
await bankSyncPage.searchFor('NonExistentAccount123456789');
const emptyMessage = page.getByText(/No accounts found/);
await expect(emptyMessage).toBeVisible();
await expect(page).toMatchThemeScreenshots();
});
});

View File

@@ -1,35 +0,0 @@
import { type Page } from '@playwright/test';
import { expect, test } from './fixtures';
import { type BankSyncPage } from './page-models/bank-sync-page';
import { ConfigurationPage } from './page-models/configuration-page';
import { Navigation } from './page-models/navigation';
test.describe('Bank Sync', () => {
let page: Page;
let navigation: Navigation;
let bankSyncPage: BankSyncPage;
let configurationPage: ConfigurationPage;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
navigation = new Navigation(page);
configurationPage = new ConfigurationPage(page);
await page.goto('/');
await configurationPage.createTestFile();
});
test.afterAll(async () => {
await page.close();
});
test.beforeEach(async () => {
bankSyncPage = await navigation.goToBankSyncPage();
});
test('checks the page visuals', async () => {
await bankSyncPage.waitToLoad();
await expect(page).toMatchThemeScreenshots();
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -1,13 +0,0 @@
import { type Page } from '@playwright/test';
export class BankSyncPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async waitToLoad() {
await this.page.waitForSelector('text=Bank Sync', { timeout: 10000 });
}
}

View File

@@ -29,14 +29,10 @@ export class CustomReportPage {
async selectMode(mode: 'total' | 'time') {
switch (mode) {
case 'total':
await this.pageContent
.getByRole('button', { name: 'Total', exact: true })
.click();
await this.pageContent.getByRole('button', { name: 'Total' }).click();
break;
case 'time':
await this.pageContent
.getByRole('button', { name: 'Time', exact: true })
.click();
await this.pageContent.getByRole('button', { name: 'Time' }).click();
break;
default:
throw new Error(`Unrecognized mode: ${mode}`);

View File

@@ -1,28 +0,0 @@
import { type Locator, type Page } from '@playwright/test';
export class MobileBankSyncPage {
readonly page: Page;
readonly searchBox: Locator;
readonly accountsList: Locator;
constructor(page: Page) {
this.page = page;
this.searchBox = page.getByPlaceholder(/Filter accounts/i);
this.accountsList = page.getByRole('main');
}
async waitFor(options?: {
state?: 'attached' | 'detached' | 'visible' | 'hidden';
timeout?: number;
}) {
await this.accountsList.waitFor(options);
}
async waitToLoad() {
await this.page.waitForSelector('text=Bank Sync', { timeout: 10000 });
}
async searchFor(term: string) {
await this.searchBox.fill(term);
}
}

View File

@@ -2,7 +2,6 @@ import { type Locator, type Page } from '@playwright/test';
import { MobileAccountPage } from './mobile-account-page';
import { MobileAccountsPage } from './mobile-accounts-page';
import { MobileBankSyncPage } from './mobile-bank-sync-page';
import { MobileBudgetPage } from './mobile-budget-page';
import { MobilePayeesPage } from './mobile-payees-page';
import { MobileReportsPage } from './mobile-reports-page';
@@ -16,7 +15,6 @@ const NAV_LINKS_HIDDEN_BY_DEFAULT = [
'Schedules',
'Payees',
'Rules',
'Bank Sync',
'Settings',
];
const ROUTES_BY_PAGE = {
@@ -26,7 +24,6 @@ const ROUTES_BY_PAGE = {
Reports: '/reports',
Payees: '/payees',
Rules: '/rules',
'Bank Sync': '/bank-sync',
Settings: '/settings',
};
@@ -185,13 +182,6 @@ export class MobileNavigation {
);
}
async goToBankSyncPage() {
return await this.navigateToPage(
'Bank Sync',
() => new MobileBankSyncPage(this.page),
);
}
async goToSettingsPage() {
return await this.navigateToPage(
'Settings',

View File

@@ -1,7 +1,6 @@
import { type Page } from '@playwright/test';
import { AccountPage } from './account-page';
import { BankSyncPage } from './bank-sync-page';
import { PayeesPage } from './payees-page';
import { ReportsPage } from './reports-page';
import { RulesPage } from './rules-page';
@@ -67,19 +66,6 @@ export class Navigation {
return new PayeesPage(this.page);
}
async goToBankSyncPage() {
const bankSyncLink = this.page.getByRole('link', { name: 'Bank Sync' });
// Expand the "more" menu only if it is not already expanded
if (!(await bankSyncLink.isVisible())) {
await this.page.getByRole('button', { name: 'More' }).click();
}
await bankSyncLink.click();
return new BankSyncPage(this.page);
}
async goToSettingsPage() {
const settingsLink = this.page.getByRole('link', { name: 'Settings' });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -59,10 +59,8 @@ test.describe('Mobile Rules', () => {
});
test('clicking on a rule opens edit form', async () => {
await expect(async () => {
const ruleCount = await rulesPage.getRuleCount();
expect(ruleCount).toBeGreaterThan(0);
}).toPass();
const ruleCount = await rulesPage.getRuleCount();
expect(ruleCount).toBeGreaterThan(0);
await rulesPage.clickRule(0);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Some files were not shown because too many files have changed in this diff Show More