Skip to content

Commit 3b36017

Browse files
committed
test(playwright): init
1 parent 66d4dce commit 3b36017

30 files changed

+2252
-2
lines changed

.github/workflows/postman-tests.yml

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Postman Tests
1+
name: e2e Tests
22

33
on:
44
pull_request:
@@ -9,7 +9,7 @@ env:
99
GITLAB_MAVEN_REGISTRY_URL: ${{ secrets.GITLAB_MAVEN_REGISTRY_URL }}
1010

1111
jobs:
12-
test:
12+
postman:
1313
runs-on: ubuntu-latest
1414

1515
steps:
@@ -49,3 +49,42 @@ jobs:
4949
run: |
5050
newman run postman/Reeve_Integration.postman_collection.json \
5151
--environment postman/Reeve_env.postman_environment.json
52+
53+
playwright:
54+
runs-on: ubuntu-latest
55+
56+
steps:
57+
- name: Checkout code
58+
uses: actions/checkout@v3
59+
- name: Checkout cf-reeve-db-migrations
60+
uses: actions/checkout@v4
61+
with:
62+
repository: ${{ env.REEVE_DB_MIGRATIONS_REPO }}
63+
ref: ${{ env.REEVE_DB_MIGRATIONS_REF }}
64+
ssh-key: ${{ secrets.CF_REEVE_DB_MIGRATIONS_SSH_DEPLOY_KEY }}
65+
path: ${{ env.REEVE_DB_MIGRATIONS_PATH }}
66+
67+
- name: Set up Docker Compose
68+
run: |
69+
docker compose build --build-arg GITLAB_MAVEN_REGISTRY_URL="${GITLAB_MAVEN_REGISTRY_URL}"
70+
docker compose up -d
71+
timeout 150 docker compose logs -f api || true
72+
- name: Services health-check API
73+
run: |
74+
timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:9000/swagger-ui/index.html)" != "200" ]]; do sleep 2;echo "."; done'
75+
echo "API is up"
76+
docker ps
77+
- name: Services health-check Keycloak
78+
run: |
79+
timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/realms/master)" != "200" ]]; do sleep 2;echo "."; done'
80+
echo "Keycloak is up"
81+
docker ps
82+
- name: Set up Node.js
83+
uses: actions/setup-node@v3
84+
with:
85+
node-version: 24
86+
- name: Install Playwright dependencies
87+
working-directory: playwright
88+
run: npm install
89+
- name: Run Playwright e2e tests
90+
run: npm run test

playwright/.gitignore

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/node_modules
2+
/.pnp
3+
.pnp.js
4+
5+
# testing
6+
/coverage
7+
.auth/
8+
.logs/
9+
.reports/
10+
/resources/
11+
allure-results
12+
**/.features-gen/**/*.spec.js
13+
14+
# production
15+
/build
16+
17+
# misc
18+
.DS_Store
19+
../.env
20+
21+
# Playwright
22+
node_modules/
23+
/test-results/
24+
/playwright-report/
25+
/blob-report/
26+
/playwright/.cache/
27+
/playwright/resources
28+

playwright/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# 🧪 Reeve API Automation Framework
2+
3+
This project uses [Playwright](https://playwright.dev/) with BDD-style tests using [playwright-bdd](https://github.com/folke/playwright-bdd).
4+
5+
## 📦 Installation
6+
7+
To install all dependencies, including Playwright and playwright-bdd:
8+
9+
- Install Playwright and Playwright-BDD
10+
```
11+
npm i -D @playwright/test playwright-bdd
12+
```
13+
```
14+
npx playwright install
15+
```
16+
- Install only Playwright-BDD
17+
```
18+
npm i -D playwright-bdd
19+
```
20+
## Env file to run in local
21+
22+
1. Create a `.env` file at the root of the playwright folder.
23+
2. Use next structure as example.
24+
3. Ask a team member for the required environment variables & corresponding values for the API, KEYCLOAK and extra application necessary variables.
25+
26+
```
27+
API_URL= <API env url>
28+
LOGIN_URL= <KEYCLOAK url>
29+
MANAGER_USER= <Manager user name>
30+
MANAGER_PASSWORD= <Manager user password>
31+
API_LOG_REQUEST= <Boolean flag to show in logger the request body and params>
32+
ORGANIZATION_ID= <Organization ID>
33+
```
34+
35+
## ⚙️ Test run in local:
36+
37+
### Run all tests in feature files
38+
npm test
39+
### You can run a specific .feature file by passing it as an argument:
40+
npm test "your-feature-file.feature"
41+
42+
## 📂 Test Structure
43+
44+
The test suite follows a BDD-style structure using [Gherkin](https://cucumber.io/docs/gherkin/)
45+
feature files and corresponding step definitions.
46+
47+
### 🔸 Folder layout
48+
- `tests/`
49+
- `e2e/`: Contains `.feature` files written in Gherkin syntax. Each file describes user scenarios using Given, When, and Then steps.
50+
- `test-scenarios.feature`
51+
- `other-tests.feature`
52+
- `steps/`: Contains the step definitions — the TypeScript code that implements the behavior described in the feature files.
53+
- `test-scenarios.steps.ts`
54+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export enum BatchesStatusCodes {
2+
APPROVE = "APPROVE",
3+
PENDING = "PENDING",
4+
INVALID = "INVALID",
5+
PUBLISH = "PUBLISH",
6+
PUBLISHED = "PUBLISHED"
7+
8+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as process from "process";
2+
3+
export class Reeve {
4+
static readonly BASE_URL = process.env.API_URL as string;
5+
static readonly LOGIN_URL = process.env.LOGIN_URL as string;
6+
7+
static SignIn = class {
8+
public static get Base() {
9+
return `${Reeve.LOGIN_URL}/realms/reeve-master/protocol/openid-connect/token`;
10+
}
11+
};
12+
static Transactions = class {
13+
public static get Types() {
14+
return `${Reeve.BASE_URL}/transaction-types`
15+
}
16+
public static get Extraction() {
17+
return `${Reeve.BASE_URL}/extraction`
18+
}
19+
public static get Validation() {
20+
return `${Reeve.Transactions.Extraction}/validation`
21+
}
22+
}
23+
static Organization = class {
24+
public static get Base() {
25+
return `${Reeve.BASE_URL}/organisations`
26+
}
27+
public static get EventCodes() {
28+
return `${Reeve.Organization.Base}/:orgId/event-codes`
29+
}
30+
public static get ChartOfAccounts() {
31+
return `${Reeve.Organization.Base}/:orgId/chart-of-accounts`
32+
}
33+
}
34+
static Batches = class {
35+
public static get Batches() {
36+
return `${Reeve.BASE_URL}/batches`
37+
}
38+
public static get BatchById() {
39+
return `${Reeve.Batches.Batches}/:batchId`
40+
}
41+
}
42+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum HttpStatusCodes {
2+
success = 200,
3+
RequestAccepted = 202,
4+
BadRequest = 400,
5+
Unauthorized= 401
6+
}

playwright/api/base.api.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {APIRequestContext, APIResponse} from "@playwright/test";
2+
3+
import {log} from "../utils/logger";
4+
5+
const returnLoggedResponse = async (
6+
response: APIResponse,
7+
endpoint: string,
8+
payload?: object,
9+
isBodyNotSecret = true
10+
) => {
11+
log.info(`Request URL: ${endpoint}`);
12+
if (typeof payload !== "undefined" && isBodyNotSecret) {
13+
log.info(`Request params/body:\n${JSON.stringify(payload, null, 2)}`);
14+
}
15+
log.info(`Response status: ${response.status()}`);
16+
if (response.headers()["content-type"]?.includes("application/json") && isBodyNotSecret) {
17+
log.info(`Response body:\n${JSON.stringify(await response.json(), null, 2)}`);
18+
}
19+
return response;
20+
};
21+
22+
export const postForm = async (
23+
request: APIRequestContext,
24+
endpoint: string,
25+
form?: { [key: string]: string },
26+
headers?: { [key: string]: string },
27+
isBodyNotSecret = true
28+
) =>
29+
returnLoggedResponse(
30+
await request.post(endpoint, {
31+
form,
32+
headers
33+
}),
34+
endpoint,
35+
form,
36+
isBodyNotSecret
37+
);
38+
export const getData = async (
39+
request: APIRequestContext,
40+
endpoint: string,
41+
params?: { [key: string]: string | number | boolean },
42+
headers?: { [key: string]: string },
43+
isBodyNotSecret = true
44+
) =>
45+
returnLoggedResponse(
46+
await request.get(endpoint, {
47+
headers,
48+
params
49+
}),
50+
endpoint,
51+
params,
52+
isBodyNotSecret
53+
);
54+
export const postFormData = async (
55+
request: APIRequestContext,
56+
endpoint: string,
57+
multipart?: {[key: string]: any},
58+
headers?: {[key: string]: string},
59+
isBodyNotSecret = true
60+
) => {
61+
return returnLoggedResponse(
62+
await request.post(endpoint, {
63+
headers,
64+
multipart
65+
}),
66+
endpoint,
67+
multipart,
68+
isBodyNotSecret
69+
);
70+
}
71+
export const postData = async (
72+
request: APIRequestContext,
73+
endpoint: string,
74+
data?: {[key: string]: any},
75+
headers?: {[key: string]: string},
76+
params?: { [key: string]: string | number | boolean },
77+
isBodyNotSecret = true
78+
) => {
79+
return returnLoggedResponse(
80+
await request.post(endpoint,{
81+
headers,
82+
data
83+
}),
84+
endpoint,
85+
data,
86+
isBodyNotSecret
87+
)
88+
}

playwright/api/dtos/batchDto.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
export interface BatchStatistics {
3+
batchId: string;
4+
invalid: number;
5+
pending: number;
6+
approve: number;
7+
publish: number;
8+
published: number;
9+
total: number;
10+
}
11+
12+
export interface FilteringParameters {
13+
transactionTypes: string[];
14+
from: string;
15+
to: string;
16+
accountingPeriodFrom: string;
17+
accountingPeriodTo: string;
18+
transactionNumbers: string[];
19+
}
20+
21+
export interface TransactionItem {
22+
id: string;
23+
accountDebitCode: string;
24+
accountDebitName: string;
25+
accountDebitRefCode: string;
26+
accountCreditCode: string;
27+
accountCreditName: string;
28+
accountCreditRefCode: string;
29+
amountFcy: number;
30+
amountLcy: number;
31+
fxRate: number;
32+
costCenterCustomerCode: string;
33+
costCenterName: string;
34+
parentCostCenterCustomerCode: string;
35+
parentCostCenterName: string;
36+
projectCustomerCode: string;
37+
projectName: string;
38+
parentProjectCustomerCode: string;
39+
parentProjectName: string;
40+
accountEventCode: string;
41+
accountEventName: string;
42+
documentNum: string;
43+
documentCurrencyCustomerCode: string;
44+
vatCustomerCode: string;
45+
vatRate: number;
46+
counterpartyCustomerCode: string;
47+
counterpartyType: string;
48+
counterpartyName: string;
49+
}
50+
51+
export interface Violation {
52+
severity: string;
53+
source: string;
54+
transactionItemId: string;
55+
code: string;
56+
bag: {
57+
customerCode: string;
58+
transactionNumber: string;
59+
};
60+
}
61+
62+
export interface Transaction {
63+
id: string;
64+
internalTransactionNumber: string;
65+
entryDate: string;
66+
transactionType: string;
67+
dataSource: string;
68+
status: string;
69+
statistic: string;
70+
validationStatus: string;
71+
ledgerDispatchStatus: string;
72+
transactionApproved: boolean;
73+
ledgerDispatchApproved: boolean;
74+
amountTotalLcy: number;
75+
itemRejection: boolean;
76+
reconciliationSource: string;
77+
reconciliationSink: string;
78+
reconciliationFinalStatus: string;
79+
reconciliationRejectionCode: string[];
80+
itemCount: number;
81+
items: TransactionItem[];
82+
violations: Violation[];
83+
}
84+
85+
export interface BatchResponse {
86+
id: string;
87+
createdAt: string;
88+
updatedAt: string;
89+
createdBy: string;
90+
updateBy: string;
91+
organisationId: string;
92+
status: string;
93+
batchStatistics: BatchStatistics;
94+
filteringParameters: FilteringParameters;
95+
transactions: Transaction[];
96+
details: Record<string, any>;
97+
totalTransactionsCount: number;
98+
}

0 commit comments

Comments
 (0)