mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-21 15:36:50 -05:00
test: bare-bones e2e testing with playwright (#416)
This commit is contained in:
committed by
GitHub
parent
4b90e37186
commit
50a71335e6
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -8,7 +8,7 @@ runs:
|
||||
with:
|
||||
node-version: 16.15.0
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
|
||||
41
.github/workflows/e2e-test.yml
vendored
Normal file
41
.github/workflows/e2e-test.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: E2E Tests
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
env:
|
||||
GITHUB_PR_NUMBER: ${{github.event.pull_request.number}}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run end-to-end tests on Netlify PR preview
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Playwright
|
||||
run: npx playwright install --with-deps
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Wait for Pages changed to neutral
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
id: wait-for-Netlify
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
checkName: 'Pages changed - actualbudget'
|
||||
- name: Waiting for Netlify Preview
|
||||
if: steps.wait-for-Netlify.outputs.conclusion == 'neutral'
|
||||
uses: jakepartusch/wait-for-netlify-action@v1.4
|
||||
id: waitFor200
|
||||
with:
|
||||
site_name: 'actualbudget'
|
||||
max_timeout: 240
|
||||
- name: Run E2E Tests on Netlify URL
|
||||
run: yarn e2e
|
||||
env:
|
||||
E2E_START_URL: https://deploy-preview-${{env.GITHUB_PR_NUMBER}}--actualbudget.netlify.app
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: desktop-client-test-results
|
||||
path: packages/desktop-client/test-results/
|
||||
retention-days: 30
|
||||
@@ -27,6 +27,7 @@
|
||||
"start:browser-frontend": "yarn workspace @actual-app/web start:browser",
|
||||
"test": "yarn workspaces foreach --parallel --verbose run test",
|
||||
"test:debug": "yarn workspaces foreach --verbose run test",
|
||||
"e2e": "yarn workspaces foreach --parallel --verbose run e2e",
|
||||
"rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core",
|
||||
"rebuild-node": "yarn workspace loot-core rebuild",
|
||||
"lint": "yarn workspaces foreach --verbose run lint --max-warnings 0",
|
||||
|
||||
1
packages/desktop-client/.gitignore
vendored
1
packages/desktop-client/.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules
|
||||
|
||||
# testing
|
||||
coverage
|
||||
test-results
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
@@ -1 +1,21 @@
|
||||
Actual on the web
|
||||
|
||||
## E2E tests
|
||||
|
||||
E2E (end-to-end) tests use [Playwright](https://playwright.dev/). Running them requires an Actual server to be running either locally or on a remote server.
|
||||
|
||||
Running against the local server:
|
||||
|
||||
```sh
|
||||
# Start the development server
|
||||
yarn start:browser
|
||||
|
||||
# Run against the local server (localhost:3001)
|
||||
yarn e2e
|
||||
```
|
||||
|
||||
Running against a remote server:
|
||||
|
||||
```sh
|
||||
E2E_START_URL=http://my-remote-server.com yarn e2e
|
||||
```
|
||||
|
||||
114
packages/desktop-client/e2e/page-models/account-page.js
Normal file
114
packages/desktop-client/e2e/page-models/account-page.js
Normal file
@@ -0,0 +1,114 @@
|
||||
export class AccountPage {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
|
||||
this.addNewTransactionButton = this.page.getByRole('button', {
|
||||
name: 'Add New'
|
||||
});
|
||||
this.newTransactionRow = this.page
|
||||
.getByTestId('new-transaction')
|
||||
.getByTestId('row');
|
||||
this.addTransactionButton = this.page.getByTestId('add-button');
|
||||
this.cancelTransactionButton = this.page.getByRole('button', {
|
||||
name: 'Cancel'
|
||||
});
|
||||
|
||||
this.transactionTableRow = this.page
|
||||
.getByTestId('table')
|
||||
.getByTestId('row');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single transaction
|
||||
*/
|
||||
async createSingleTransaction(transaction) {
|
||||
await this.addNewTransactionButton.click();
|
||||
|
||||
await this._fillTransactionFields(this.newTransactionRow, transaction);
|
||||
|
||||
await this.addTransactionButton.click();
|
||||
await this.cancelTransactionButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create split transactions
|
||||
*/
|
||||
async createSplitTransaction([rootTransaction, ...transactions]) {
|
||||
await this.addNewTransactionButton.click();
|
||||
|
||||
// Root transaction
|
||||
const transactionRow = this.newTransactionRow.first();
|
||||
await this._fillTransactionFields(transactionRow, {
|
||||
...rootTransaction,
|
||||
category: 'split'
|
||||
});
|
||||
|
||||
// Child transactions
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
await this._fillTransactionFields(
|
||||
this.newTransactionRow.nth(i + 1),
|
||||
transactions[i]
|
||||
);
|
||||
|
||||
if (i + 1 < transactions.length) {
|
||||
await this.page.getByRole('button', { name: 'Add Split' }).click();
|
||||
}
|
||||
}
|
||||
|
||||
await this.addTransactionButton.click();
|
||||
await this.cancelTransactionButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data for the nth-transaction.
|
||||
* 0-based index
|
||||
*/
|
||||
async getNthTransaction(index) {
|
||||
const row = this.transactionTableRow.nth(index);
|
||||
|
||||
return {
|
||||
payee: await row.getByTestId('payee').textContent(),
|
||||
notes: await row.getByTestId('notes').textContent(),
|
||||
category: await row.getByTestId('category').textContent(),
|
||||
debit: await row.getByTestId('debit').textContent(),
|
||||
credit: await row.getByTestId('credit').textContent()
|
||||
};
|
||||
}
|
||||
|
||||
async _fillTransactionFields(transactionRow, transaction) {
|
||||
if (transaction.payee) {
|
||||
await transactionRow.getByTestId('payee').click();
|
||||
await this.page.keyboard.type(transaction.payee);
|
||||
await this.page.keyboard.press('Tab');
|
||||
}
|
||||
|
||||
if (transaction.notes) {
|
||||
await transactionRow.getByTestId('notes').click();
|
||||
await this.page.keyboard.type(transaction.notes);
|
||||
await this.page.keyboard.press('Tab');
|
||||
}
|
||||
|
||||
if (transaction.category) {
|
||||
await transactionRow.getByTestId('category').click();
|
||||
|
||||
if (transaction.category === 'split') {
|
||||
await this.page.getByTestId('split-transaction-button').click();
|
||||
} else {
|
||||
await this.page.keyboard.type(transaction.category);
|
||||
await this.page.keyboard.press('Tab');
|
||||
}
|
||||
}
|
||||
|
||||
if (transaction.debit) {
|
||||
await transactionRow.getByTestId('debit').click();
|
||||
await this.page.keyboard.type(transaction.debit);
|
||||
await this.page.keyboard.press('Tab');
|
||||
}
|
||||
|
||||
if (transaction.credit) {
|
||||
await transactionRow.getByTestId('credit').click();
|
||||
await this.page.keyboard.type(transaction.credit);
|
||||
await this.page.keyboard.press('Tab');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export class ConfigurationPage {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async createTestFile() {
|
||||
await this.page.getByRole('button', { name: 'Create test file' }).click();
|
||||
await this.page.getByRole('button', { name: 'Close' }).click();
|
||||
}
|
||||
}
|
||||
15
packages/desktop-client/e2e/page-models/navigation.js
Normal file
15
packages/desktop-client/e2e/page-models/navigation.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AccountPage } from './account-page';
|
||||
|
||||
export class Navigation {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async goToAccountPage(accountName) {
|
||||
await this.page
|
||||
.getByRole('link', { name: new RegExp(`^${accountName}`) })
|
||||
.click();
|
||||
|
||||
return new AccountPage(this.page);
|
||||
}
|
||||
}
|
||||
84
packages/desktop-client/e2e/transactions.test.js
Normal file
84
packages/desktop-client/e2e/transactions.test.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { Navigation } from './page-models/navigation';
|
||||
|
||||
test.describe('Transactions', () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let accountPage;
|
||||
let 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 () => {
|
||||
accountPage = await navigation.goToAccountPage('Ally Savings');
|
||||
});
|
||||
|
||||
test('creates a test transaction', async () => {
|
||||
await accountPage.createSingleTransaction({
|
||||
payee: 'Home Depot',
|
||||
notes: 'Notes field',
|
||||
category: 'Food',
|
||||
debit: '12.34'
|
||||
});
|
||||
|
||||
expect(await accountPage.getNthTransaction(0)).toMatchObject({
|
||||
payee: 'Home Depot',
|
||||
notes: 'Notes field',
|
||||
category: 'Food',
|
||||
debit: '12.34',
|
||||
credit: ''
|
||||
});
|
||||
});
|
||||
|
||||
test('creates a split test transaction', async () => {
|
||||
await accountPage.createSplitTransaction([
|
||||
{
|
||||
payee: 'Krogger',
|
||||
notes: 'Notes',
|
||||
debit: '333.33'
|
||||
},
|
||||
{
|
||||
category: 'General',
|
||||
debit: '222.22'
|
||||
},
|
||||
{
|
||||
debit: '111.11'
|
||||
}
|
||||
]);
|
||||
|
||||
expect(await accountPage.getNthTransaction(0)).toMatchObject({
|
||||
payee: 'Krogger',
|
||||
notes: 'Notes',
|
||||
category: 'Split',
|
||||
debit: '333.33',
|
||||
credit: ''
|
||||
});
|
||||
expect(await accountPage.getNthTransaction(1)).toMatchObject({
|
||||
payee: 'Krogger',
|
||||
notes: '',
|
||||
category: 'General',
|
||||
debit: '222.22',
|
||||
credit: ''
|
||||
});
|
||||
expect(await accountPage.getNthTransaction(2)).toMatchObject({
|
||||
payee: 'Krogger',
|
||||
notes: '',
|
||||
category: 'Categorize',
|
||||
debit: '111.11',
|
||||
credit: ''
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,7 @@
|
||||
"@babel/core": "~7.14.3",
|
||||
"@jlongster/lively": "0.0.4",
|
||||
"@jlongster/sentry-metrics-actual": "^0.0.10",
|
||||
"@playwright/test": "^1.29.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.2",
|
||||
"@reach/listbox": "^0.11.2",
|
||||
"@reactions/component": "^2.0.2",
|
||||
@@ -86,6 +87,7 @@
|
||||
"watch": "cross-env PORT=3001 node scripts/start.js",
|
||||
"build": "cross-env INLINE_RUNTIME_CHUNK=false node scripts/build.js",
|
||||
"build:browser": "cross-env ./bin/build-browser",
|
||||
"e2e": "npx playwright test e2e --browser=chromium",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
9
packages/desktop-client/playwright.config.js
Normal file
9
packages/desktop-client/playwright.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
timeout: 20000, // 20 seconds
|
||||
retries: 1,
|
||||
use: {
|
||||
screenshot: 'on',
|
||||
browserName: 'chromium',
|
||||
baseURL: process.env.E2E_START_URL ?? 'http://localhost:3001'
|
||||
}
|
||||
};
|
||||
@@ -938,6 +938,7 @@ export function Modal({
|
||||
bare
|
||||
onClick={e => onClose()}
|
||||
style={{ padding: '10px 10px' }}
|
||||
aria-label="Close"
|
||||
>
|
||||
<Delete width={10} />
|
||||
</Button>
|
||||
|
||||
@@ -483,6 +483,7 @@ const MenuButton = withRouter(function MenuButton({ history }) {
|
||||
}}
|
||||
activeStyle={{ color: colors.p7 }}
|
||||
onClick={() => setMenuOpen(true)}
|
||||
aria-label="Menu"
|
||||
>
|
||||
<DotsHorizontalTriple
|
||||
width={15}
|
||||
|
||||
22
yarn.lock
22
yarn.lock
@@ -55,6 +55,7 @@ __metadata:
|
||||
"@babel/core": ~7.14.3
|
||||
"@jlongster/lively": 0.0.4
|
||||
"@jlongster/sentry-metrics-actual": ^0.0.10
|
||||
"@playwright/test": ^1.29.1
|
||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.4.2
|
||||
"@reach/listbox": ^0.11.2
|
||||
"@react-aria/focus": ^3.8.0
|
||||
@@ -3216,6 +3217,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@playwright/test@npm:^1.29.1":
|
||||
version: 1.29.1
|
||||
resolution: "@playwright/test@npm:1.29.1"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
playwright-core: 1.29.1
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: c4424ee09f6589a296a3a78402f8c9eac841f3e5f44d96e3a2ba07aaa00d53ee9c10cc96379b304743a9e70f231df0ded0ad2fc197f412ada67b4cf17ee24b0f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@npm:^0.4.2":
|
||||
version: 0.4.3
|
||||
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.4.3"
|
||||
@@ -18205,6 +18218,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.29.1":
|
||||
version: 1.29.1
|
||||
resolution: "playwright-core@npm:1.29.1"
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: e1c8423db4d28f44e5365a353f321b04a07709c8bf26d0c908f06d2c2669f715315170f84ca98a532951c2ac6324f5f48f651dca1abe43092c7637bdb4703303
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"please-upgrade-node@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "please-upgrade-node@npm:3.2.0"
|
||||
|
||||
Reference in New Issue
Block a user