├── .DS_Store
├── .eslintrc.js
├── .github
├── FUNDING.yml
└── workflows
│ ├── npm-publish.yml
│ └── run-tests.yml
├── .gitignore
├── .gitlab-ci.yml
├── .travis.yml
├── README.md
├── _ci
├── api-load-test.yaml
└── api-test.yaml
├── assets
└── Screenshot 2023-08-09 at 10.30.37.png
├── codecept.conf.ts
├── docker
├── Dockerfile
├── docker-compose.yml
├── prometheus
│ └── prometheus.yml
└── run-tests.sh
├── factories
└── user.ts
├── fixtures
└── constants.ts
├── helpers
└── allure-report.helper.ts
├── jenkins
├── Jenkinsfile
└── run-tests.sh
├── load-tests
└── scenario-1.js
├── package.json
├── prometheus
├── processData.ts
└── sendData.js
├── src
├── DELETE_test.ts
├── GET_test.ts
├── POST_test.ts
├── PUT_test.ts
└── fixtures
│ └── test_image.png
├── stepObjects
├── custom.steps.ts
└── userSteps.ts
├── steps.d.ts
└── tsconfig.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kobenguyent/codeceptjs-rest-demo/5330bae48d7a22f8eae56ecc6103757f861c3c03/.DS_Store
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": ["plugin:codeceptjs/recommended", "eslint:recommended"],
8 | "parserOptions": {
9 | "sourceType": "module",
10 | "ecmaVersion": 2017
11 | },
12 | "rules": {
13 | "indent": [
14 | "error",
15 | "tab"
16 | ],
17 | "linebreak-style": [
18 | "error",
19 | "unix"
20 | ],
21 | "quotes": [
22 | "error",
23 | "single"
24 | ],
25 | "semi": [
26 | "error",
27 | "always"
28 | ],
29 | "no-control-regex": 0
30 | }
31 | };
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: kobenguyent
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: Use Central Publish Workflow
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 |
9 | jobs:
10 | call-publish:
11 | uses: kobenguyent/actionsmith/.github/workflows/publish-npm.yml@main
12 | secrets:
13 | npm_token: ${{ secrets.npm_token }}
14 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: API Tests - Allure Reports
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - '!master' # ignore gh-pages
7 |
8 | env:
9 | NODE_VERSION: 22
10 | CI_BUILD_NUMBER: ${{ github.run_number }}
11 |
12 | permissions:
13 | contents: write
14 | pages: write
15 | id-token: write
16 | actions: read
17 |
18 | concurrency:
19 | group: 'pages'
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | lint-check:
24 | uses: kobenguyent/actionsmith/.github/workflows/lint-check.yml@main
25 |
26 | acceptance-tests:
27 | needs: [lint-check]
28 |
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v3
32 | - uses: actions/setup-node@v3
33 | with:
34 | node-version: ${{ env.NODE_VERSION }}
35 | registry-url: https://registry.npmjs.org/
36 |
37 | - name: Install dependencies
38 | run: |
39 | npm i
40 | npx playwright install
41 |
42 | - name: Run tests and generate Allure report
43 | run: |
44 | npm run test
45 | npm run allure-reports-generate
46 | #execute this command for local to access allure reports
47 | #npm run allure-reports-patch
48 | env:
49 | MAILINATOR_TOKEN: ${{ secrets.MAILINATOR_TOKEN }}
50 | MAILINATOR_DOMAIN: ${{ secrets.MAILINATOR_DOMAIN }}
51 | generate-and-deploy-allure-report:
52 | needs: [acceptance-tests]
53 |
54 | uses: kobenguyent/actionsmith/.github/workflows/deploy-allure-gh-pages.yml@main
55 | with:
56 | allure_results: output
57 | allure_history: allure-history/allure-history
58 | publish_dir: allure-history/allure-history
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | output/*
3 | package-lock.json
4 | report/*
5 | .idea
6 | dist
7 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: peternguyentr/playwright-node-java:latest
2 |
3 | stages:
4 | - test
5 |
6 | include:
7 | - local: /_ci/api-test.yaml
8 | - local: /_ci/api-load-test.yaml
9 |
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 16
4 | sudo: required
5 | dist: trusty
6 | script:
7 | - npm i
8 | - npm test
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://paypal.me/peternguyentr?country.x=DE&locale.x=en_US)
2 |
3 | [](https://www.codacy.com/manual/PeterNgTr/codeceptjs-rest-demo?utm_source=github.com&utm_medium=referral&utm_content=PeterNgTr/codeceptjs-rest-demo&utm_campaign=Badge_Grade)[](https://www.buymeacoffee.com/peternguyew)
4 |
5 | # Introduction
6 |
7 | This project demonstrates how to use CodeceptJS with REST helper.
8 |
9 | ## How to use
10 |
11 | This is done using CodeceptJS
12 |
13 | ### Tech
14 |
15 | This test uses a number of open source projects to work properly:
16 |
17 | * - evented I/O for the backend
18 | * - CodeceptJS
19 | * - Endpoints that are used in this project
20 |
21 | ### Installation
22 |
23 | This requires [Node.js](https://nodejs.org/) v16+ to run.
24 |
25 | Install the dependencies and devDependencies.
26 |
27 | ```sh
28 | cd codeceptjs-rest-demo
29 | npm i
30 | ```
31 |
32 | ### How to trigger API tests
33 |
34 | To run all api tests just simply type
35 |
36 | ```sh
37 | npm test
38 | ```
39 |
40 | Example output
41 |
42 | ```sh
43 | CodeceptJS v3.4.1 #StandWithUkraine
44 | Using test root "/Users/thanh.nguyen/Desktop/codeceptjs-rest-demo"
45 |
46 | DELETE tests --
47 | ✔ Verify deleting a user in 136ms
48 | GET tests --
49 | ✔ Verify a successful call in 126ms
50 | ✔ Verify a not found call in 124ms
51 | ✔ Verify getting a single user in 180ms
52 | ✔ Verify getting list of users in 65ms
53 | POST tests --
54 | ✔ Verify creating new user in 131ms
55 | ✔ Verify uploading a file in 5789ms
56 | PUT tests --
57 | ✔ Verify creating new user in 118ms
58 |
59 | OK | 8 passed // 7s
60 | ```
61 |
62 | ### Report
63 | Allure report: https://kobenguyent.github.io/codeceptjs-rest-demo/
64 |
65 | ### E2E Dashboard using Grafana/Prometheus
66 |
67 | After generating Allure report, it would also come with data which we could use to send to time series database Prometheus for example.
68 |
69 | What do you need?
70 | - Docker installed
71 | - Basic knowledge on how to set up the Dashboard on Grafana, query with Prometheus
72 |
73 | To start the Grafana/Prometheus, run this command
74 |
75 | ```
76 | cd docker && docker-compose up
77 |
78 | ➜ docker git:(master) ✗ docker-compose up
79 | [+] Running 3/0
80 | ✔ Container docker-pushgateway-1 Created 0.0s
81 | ✔ Container docker-grafana-1 Created 0.0s
82 | ✔ Container docker-prometheus-1 Created 0.0s
83 | Attaching to docker-grafana-1, docker-prometheus-1, docker-pushgateway-1
84 | docker-pushgateway-1 | ts=2023-08-08T09:49:48.904Z caller=main.go:86 level=info msg="starting pushgateway" version="(version=1.6.0, branch=HEAD, revision=2b1a354b9e6f2cfab172767cd3e26d92efa9dccf)"
85 | docker-pushgateway-1 | ts=2023-08-08T09:49:48.904Z caller=main.go:87 level=info build_context="(go=go1.20.4, platform=linux/arm64, user=root@b93562bc57fd, date=20230525-11:20:38, tags=netgo)"
86 | docker-pushgateway-1 | ts=2023-08-08T09:49:48.907Z caller=tls_config.go:274 level=info msg="Listening on" address=[::]:9091
87 | docker-pushgateway-1 | ts=2023-08-08T09:49:48.907Z caller=tls_config.go:277 level=info msg="TLS is disabled." http2=false address=[::]:9091
88 | docker-grafana-1 | logger=settings t=2023-08-08T09:49:49.087865552Z level=info msg="Starting Grafana" version=10.0.3 commit=eb8dd72637 branch=HEAD compiled=2023-07-25T17:55:59Z
89 |
90 | ```
91 | Grafana is now accessible via http://localhost:3000/
92 |
93 | One command to run tests and sending data to Prometheus
94 |
95 | ```
96 | npm run test && npm run allure-reports-generate && npm run processData && npm run sendData
97 | ```
98 |
99 | Set up your dashboard as your preference, for example
100 |
101 | Some query for your references:
102 |
103 | TCs per build:
104 |
105 | ```
106 | sum by (build) ({__name__=~"e2e_tests_status_(passed|failed|broken)", build!="unknown"})
107 | ```
108 |
109 | Execution time per build:
110 |
111 | ```
112 | sum by (build) ({__name__="e2e_tests_time_duration", build!="unknown"})
113 | ```
114 |
115 | 
116 |
--------------------------------------------------------------------------------
/_ci/api-load-test.yaml:
--------------------------------------------------------------------------------
1 | api-load-test:
2 | stage: test
3 | script:
4 | - npm run api-load-test
5 |
--------------------------------------------------------------------------------
/_ci/api-test.yaml:
--------------------------------------------------------------------------------
1 | api-test:
2 | stage: test
3 | before_script:
4 | - npm i
5 | script:
6 | - npm run test
7 | after_script:
8 | - npm run generate-report
9 | - cp -r allure-report/ ci_artifacts/
10 | artifacts:
11 | name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME"
12 | paths:
13 | - ci_artifacts/
14 | when: always
15 |
--------------------------------------------------------------------------------
/assets/Screenshot 2023-08-09 at 10.30.37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kobenguyent/codeceptjs-rest-demo/5330bae48d7a22f8eae56ecc6103757f861c3c03/assets/Screenshot 2023-08-09 at 10.30.37.png
--------------------------------------------------------------------------------
/codecept.conf.ts:
--------------------------------------------------------------------------------
1 | import {defaultHeaders} from "./fixtures/constants";
2 |
3 | export const config: CodeceptJS.MainConfig = {
4 | tests: './*/*_test.ts',
5 | output: './output',
6 | emptyOutputFolder: true,
7 | helpers: {
8 | REST: {
9 | endpoint: 'https://reqres.in',
10 | timeout: 30_000,
11 | onRequest: (request) => {
12 | request.headers = {...request.headers, ...defaultHeaders };
13 | }
14 | },
15 | AllureReport: {
16 | require: './helpers/allure-report.helper.ts'
17 | },
18 | JSONResponse: {},
19 | ExpectHelper: {
20 | require: 'codeceptjs-expect'
21 | },
22 | ApiDataFactory: {
23 | endpoint: "https://reqres.in",
24 | cleanup: false,
25 | headers: {
26 | 'Content-Type': 'application/json',
27 | 'Accept': 'application/json',
28 | },
29 | factories: {
30 | user: {
31 | factory: "./factories/user",
32 | create: (data) => ({ method: 'POST', url: '/api/users', data, headers: { ...defaultHeaders } }),
33 | },
34 | }
35 | }
36 | },
37 | include: {
38 | I: './stepObjects/custom.steps.ts'
39 | },
40 | name: 'codeceptjs-rest-demo',
41 | plugins: {
42 | allure: {
43 | enabled: true,
44 | require: '@codeceptjs/allure-legacy',
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM peternguyentr/playwright-node-java:latest
2 |
3 | # Fix certificate issues
4 | RUN apt-get update --no-install-recommends && \
5 | apt-get -y install ca-certificates-java && \
6 | apt-get clean && \
7 | update-ca-certificates -f;
8 |
9 | WORKDIR /app
10 | COPY . .
11 |
12 | RUN npm install
13 |
14 | CMD ["/app/docker/run-tests.sh"]
15 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | volumes:
4 | prometheus_data: {}
5 | grafana_data: {}
6 |
7 | networks:
8 | front-tier:
9 | back-tier:
10 |
11 | services:
12 | grafana:
13 | image: grafana/grafana
14 | user: "472"
15 | ports:
16 | - 3000:3000
17 | volumes:
18 | - grafana_data:/var/lib/grafana
19 | - ./grafana/provisioning/:/etc/grafana/provisioning/
20 | networks:
21 | - back-tier
22 | - front-tier
23 | restart: always
24 |
25 | prometheus:
26 | image: prom/prometheus:v2.36.2
27 | volumes:
28 | - ./prometheus/:/etc/prometheus/
29 | - prometheus_data:/prometheus
30 | command:
31 | - '--config.file=/etc/prometheus/prometheus.yml'
32 | - '--storage.tsdb.path=/prometheus'
33 | - '--web.console.libraries=/usr/share/prometheus/console_libraries'
34 | - '--web.console.templates=/usr/share/prometheus/consoles'
35 | ports:
36 | - 9090:9090
37 | links:
38 | - pushgateway:pushgateway
39 | depends_on:
40 | - pushgateway
41 | networks:
42 | - back-tier
43 | restart: always
44 |
45 | pushgateway:
46 | image: prom/pushgateway
47 | restart: always
48 | expose:
49 | - 9091
50 | ports:
51 | - "9091:9091"
52 | networks:
53 | - back-tier
--------------------------------------------------------------------------------
/docker/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | # my global config
2 | global:
3 | scrape_interval: 15s # By default, scrape targets every 15 seconds.
4 | evaluation_interval: 15s # By default, scrape targets every 15 seconds.
5 | # scrape_timeout is set to the global default (10s).
6 |
7 | # Attach these labels to any time series or alerts when communicating with
8 | # external systems (federation, remote storage, Alertmanager).
9 | external_labels:
10 | monitor: 'my-project'
11 |
12 | # Load and evaluate rules in this file every 'evaluation_interval' seconds.
13 | rule_files:
14 | - 'alert.rules'
15 | # - "first.rules"
16 | # - "second.rules"
17 |
18 | # alert
19 | alerting:
20 | alertmanagers:
21 | - scheme: http
22 | static_configs:
23 | - targets:
24 | - "alertmanager:9093"
25 |
26 | # A scrape configuration containing exactly one endpoint to scrape:
27 | # Here it's Prometheus itself.
28 | scrape_configs:
29 | # The job name is added as a label `job=` to any timeseries scraped from this config.
30 |
31 | - job_name: 'prometheus'
32 |
33 | # Override the global default and scrape targets from this job every 5 seconds.
34 | scrape_interval: 15s
35 |
36 | static_configs:
37 | - targets: ['localhost:9090']
38 |
39 | - job_name: 'pushgateway'
40 |
41 | # Override the global default and scrape targets from this job every 5 seconds.
42 | scrape_interval: 10s
43 |
44 | static_configs:
45 | - targets: ['pushgateway:9091']
46 |
--------------------------------------------------------------------------------
/docker/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | npm run load-test && npx codeceptjs run --steps
4 | ls report
--------------------------------------------------------------------------------
/factories/user.ts:
--------------------------------------------------------------------------------
1 | const { Factory } = require('rosie');
2 | let faker = require('faker');
3 |
4 | module.exports = new Factory()
5 | .attr('name', () => faker.name.findName())
6 | .attr('job', () => 'leader');
--------------------------------------------------------------------------------
/fixtures/constants.ts:
--------------------------------------------------------------------------------
1 | export const defaultHeaders = { 'x-api-key': 'reqres-free-v1' }
--------------------------------------------------------------------------------
/helpers/allure-report.helper.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from "fs";
2 | import uaParser from "ua-parser-js";
3 | import { Helper } from 'codeceptjs';
4 |
5 | interface UserAgentInfo {
6 | browser: {
7 | name: string;
8 | version: string;
9 | };
10 | }
11 |
12 | interface Allure {
13 | addAttachment(name: string, buffer: Buffer | string, type: string): void;
14 | }
15 |
16 | interface ExecutionEnvInfo {
17 | BASE_URL: string;
18 | BROWSER?: string;
19 | }
20 |
21 | interface ExecutorInfo {
22 | name: string;
23 | type: string;
24 | reportName: string;
25 | buildName: string;
26 | }
27 |
28 | let allure: Allure;
29 | let outputDir: string;
30 | let baseUrl: string = codeceptjs.config.get().helpers.REST.endpoint;
31 | let browserInfo: string;
32 |
33 | class AllureHelper extends Helper {
34 | constructor(config: { [key: string]: unknown }) {
35 | super(config);
36 | outputDir = codeceptjs.config.get().output;
37 | }
38 |
39 | protected async _test(): Promise {
40 | if (this.helpers.Playwright) {
41 | const getUA: string = await this.helpers.Playwright.page.evaluate(
42 | () => navigator.userAgent
43 | );
44 | const userAgentInfo: UserAgentInfo = uaParser(getUA);
45 | browserInfo = `${userAgentInfo.browser.name}-v${userAgentInfo.browser.version}`;
46 | }
47 | }
48 |
49 | protected async _finishTest(): Promise {
50 | let environment: ExecutionEnvInfo = {
51 | BASE_URL: process.env.BASE_URL || baseUrl,
52 | };
53 |
54 | if (this.helpers.REST) {
55 | environment['API'] = 'REST Helper'
56 | } else {
57 | environment['BROWSER'] = browserInfo
58 | }
59 |
60 | const executorInfo: ExecutorInfo = {
61 | name: `Local Machine`,
62 | type: "local",
63 | reportName: `Test report on ${Date.now().toString()}`,
64 | buildName: `Build #: ${process.env.CI_BUILD_NUMBER}` || `Local-${Date.now()}`,
65 | };
66 |
67 | writeFileSync(
68 | `${outputDir}/environment.properties`,
69 | Object.entries(environment)
70 | .map(([key, value]: [string, string]) => `${key}=${value}`)
71 | .join("\n")
72 | );
73 |
74 | writeFileSync(`${outputDir}/executor.json`, JSON.stringify(executorInfo));
75 | }
76 | protected async _before(): Promise {
77 | super._before();
78 | allure = codeceptjs.container.plugins("allure");
79 | }
80 | }
81 |
82 | export = AllureHelper;
83 |
--------------------------------------------------------------------------------
/jenkins/Jenkinsfile:
--------------------------------------------------------------------------------
1 | node {
2 |
3 | def imageTag = "peterngtr/rest-demo:${env.BUILD_NUMBER}"
4 | def dockerHome = tool 'myDocker'
5 |
6 | stage("Initializing") {
7 | cleanWs();
8 | checkout scm;
9 | sh 'git reset --hard'
10 | env.PATH = "${dockerHome}/bin:${env.PATH}"
11 | }
12 |
13 | stage("Building Images") {
14 | sh "docker build -t ${imageTag} -f docker/Dockerfile ."
15 | }
16 |
17 | stage("Running Tests") {
18 | try {
19 | sh "jenkins/run-tests.sh ${env.BUILD_NUMBER}"
20 | }
21 | finally {
22 | sh "ls report/"
23 | allure includeProperties: false, jdk: '', results: [[path: 'report']]
24 | }
25 | }
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/jenkins/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | VERSION=${1:-latest}
4 |
5 | echo "Pulling image ${VERSION}"
6 | mkdir report
7 |
8 | docker run --rm \
9 | -v "$(pwd)"/report/:/app/report/ \
10 | peterngtr/rest-demo:${VERSION}
11 |
12 | status=$?
13 |
14 | echo "Final status ${status}"
15 | exit ${status}
16 |
--------------------------------------------------------------------------------
/load-tests/scenario-1.js:
--------------------------------------------------------------------------------
1 | import http from 'k6/http';
2 | import { check } from 'k6';
3 |
4 | export let options = {
5 | vus: 10,
6 | duration: '6s',
7 | };
8 |
9 | export default function() {
10 | var url = 'https://reqres.in/api/users?page=2';
11 |
12 | var params = {
13 | headers: {
14 | 'Content-Type': 'application/json',
15 | },
16 | };
17 |
18 | let response = http.get(url, params);
19 |
20 | check(response, {
21 | 'is status 200': r => r.status === 200,
22 | });
23 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codeceptjs-rest-demo",
3 | "version": "1.0.0",
4 | "description": "CodeceptJS demo project with REST helper",
5 | "main": "dist/stepObjects/index.js",
6 | "scripts": {
7 | "def": "codeceptjs def",
8 | "lint": "eslint . --fix",
9 | "test": "codeceptjs run",
10 | "api-load-test": "k6 run load-tests/scenario-1.js",
11 | "compile": "tsc",
12 | "allure-reports-generate": "allure generate ./output -o ./output/allure --clean",
13 | "allure-reports-patch": "allure-patch ./output/allure",
14 | "processData": "npx ts-node prometheus/processData.ts",
15 | "sendData": "node prometheus/sendData.js"
16 | },
17 | "keywords": [
18 | "codeceptjs",
19 | "rest"
20 | ],
21 | "author": "kobenguyent",
22 | "license": "ISC",
23 | "devDependencies": {
24 | "@codeceptjs/allure-legacy": "^1.0.2",
25 | "@types/node": "^14.18.26",
26 | "codeceptjs": "3.7.3",
27 | "eslint": "^6.5.1",
28 | "eslint-plugin-codeceptjs": "^1.1.0",
29 | "faker": "^4.1.0",
30 | "rosie": "^2.1.0",
31 | "ts-node": "^8.10.2",
32 | "typescript": "^4.9.5"
33 | },
34 | "dependencies": {
35 | "allure-commandline": "^2.13.0",
36 | "allure-patch": "^1.0.3",
37 | "codeceptjs-expect": "^1.0.0",
38 | "form-data": "^3.0.0",
39 | "ua-parser-js": "^1.0.35"
40 | },
41 | "files": [
42 | "dist/stepObjects/*",
43 | "README.md"
44 | ],
45 | "repository": {
46 | "type": "git",
47 | "url": "https://github.com/kobenguyent/codeceptjs-rest-demo.git"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/prometheus/processData.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises'
2 | import path from 'path'
3 |
4 | type TimelineData = {
5 | children: {
6 | name: string
7 | status: string
8 | time: {
9 | duration: number
10 | }
11 | }[]
12 | }
13 |
14 | const allureReportPath = path.join(process.cwd(), 'output/allure')
15 | const fileList = path.join(allureReportPath, 'export', 'prometheusData.txt')
16 |
17 | const loadTimelineData = async (): Promise => {
18 | return await import(path.join(allureReportPath, 'data', 'timeline.json'))
19 | }
20 |
21 | const main = async (): Promise => {
22 | const timelineData = await loadTimelineData()
23 |
24 | const buildNumber = process.env.CI_BUILD_NUMBER || Date.now().toString();
25 | const pullRequest = process.env.DRONE_PULL_REQUEST ? 'PR' : 'unknown'
26 | const browserName = process.env.BROWSER || 'unknown'
27 |
28 | const getSuiteName = async (filePath: string): Promise => {
29 | const data = await fs.readFile(filePath, 'utf-8')
30 | const processedData = data.split('\n').filter((line): line is string => line.includes('SUITE='))
31 | if (!processedData[0]) return 'CI'
32 | return processedData[0].split('=')[1]
33 | }
34 |
35 | const suiteName = await getSuiteName('./output/environment.properties')
36 | const labels = `{build="${buildNumber}",type="${pullRequest}",suite="${suiteName}",os="linux",browser="${browserName}"}`
37 | let testData = ''
38 |
39 | const processPrometheusData = async (filePath: string): Promise => {
40 | const data = await fs.readFile(filePath, 'utf-8')
41 | const processedData = data.split('\n').map((line): string | undefined => {
42 | if (line) {
43 | const before = line.split(' ')
44 | before[0] = before[0].replace(/(^launch_)/, `e2e_tests_`) + `${labels}`
45 |
46 | return before.join(' ')
47 | }
48 | })
49 | return processedData.filter((line): line is string => !!line).join('\n') + '\n'
50 | }
51 |
52 | testData = await processPrometheusData(fileList)
53 |
54 | const processTestCaseData = (data: TimelineData): string => {
55 | let returnedData = ''
56 | const labels = `build="${buildNumber}",type="${pullRequest}",suite="${suiteName}",os="linux",browser="${browserName}",testcase="scenario"`
57 |
58 | data.children.forEach((item): void => {
59 | returnedData += `${item.name
60 | .trim()
61 | .replace(/@|\||\{|\}/g, '')
62 | .replace(/ /g, '_')}{${labels},status="${item.status}"} ${item.time.duration}\n`
63 | })
64 |
65 | return returnedData
66 | }
67 |
68 | testData += processTestCaseData(timelineData)
69 |
70 | testData = testData.replace('undefined', '')
71 |
72 | try {
73 | await fs.writeFile(fileList, testData)
74 | console.log('Data replaced \n', testData)
75 | } catch (err) {
76 | console.error('Error writing data:', err)
77 | }
78 | }
79 |
80 | main()
81 | .then((): void => {
82 | console.log('Main function completed successfully.')
83 | })
84 | .catch((err): void => {
85 | console.error('Main function failed with error:', err)
86 | })
--------------------------------------------------------------------------------
/prometheus/sendData.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const {join} = require('path');
3 |
4 | const allureReportPath = join(process.cwd().replace('/prometheus', ''), 'output/allure');
5 | const reportData = join(allureReportPath, 'export', 'prometheusData.txt');
6 |
7 | const data = fs.readFileSync(reportData, 'utf-8');
8 | data.split(/\r?\n/).forEach(line => {
9 | if (line !== '') {
10 | const { exec } = require('child_process');
11 |
12 | exec(`echo '${line}' | curl --data-binary @- http://localhost:9091/metrics/job/test`, (error, stdout, stderr) => {
13 | if (error) {
14 | console.log(`error: ${error.message}`);
15 | return;
16 | }
17 | if (stderr) {
18 | console.log(`stderr: ${stderr}`);
19 | return;
20 | }
21 | console.log(`stdout: ${stdout}`);
22 | });
23 | }
24 |
25 | });
26 |
--------------------------------------------------------------------------------
/src/DELETE_test.ts:
--------------------------------------------------------------------------------
1 | export {}
2 | const { I } = inject();
3 | let createdUser:any;
4 |
5 | Feature('DELETE tests');
6 |
7 | Before(async () => {
8 | createdUser = await I.createNewUser();
9 | });
10 |
11 | Scenario('Verify deleting a user', async () => {
12 | let id = createdUser['data']['id'];
13 | const res = await I.sendDeleteRequest(`/api/users/${id}`);
14 | I.seeResponseCodeIsSuccessful();
15 | await I.expectEqual(res.data, '');
16 | });
17 |
--------------------------------------------------------------------------------
/src/GET_test.ts:
--------------------------------------------------------------------------------
1 | const { I } = inject();
2 |
3 | Feature('GET tests');
4 |
5 | Scenario('Verify a successful call', async () => {
6 | await I.getUserPerPage(2);
7 | I.seeResponseCodeIsSuccessful();
8 | I.seeResponseContainsJson({page:2,per_page:6})
9 | });
10 |
11 | Scenario('Verify a not found call', async () => {
12 | await I.getUserById(266);
13 | I.seeResponseCodeIsClientError();
14 | });
15 |
16 | Scenario('Verify getting a single user', async () => {
17 | const res = await I.getUserById(2);
18 | await I.expectEqual(res.data.data.id, 2);
19 | });
20 |
21 | Scenario('Verify getting list of users', async () => {
22 | const res = await I.getUserPerPage(2);
23 | await I.expectEqual(res.data.data[0].id, 7);
24 | });
25 |
--------------------------------------------------------------------------------
/src/POST_test.ts:
--------------------------------------------------------------------------------
1 | export {}
2 | const fs = require('fs');
3 | const { I } = inject();
4 |
5 | Feature('POST tests');
6 |
7 | Scenario('Verify creating new user', async () => {
8 | const user = await I.have('user', null, null);
9 | I.expectNotEqual(user.id, undefined);
10 | });
11 |
12 | Scenario.skip('Verify uploading a file', async () => {
13 | const form = I.createFormData('attachment', fs.createReadStream(`${process.cwd()}/src/fixtures/test_image.png`));
14 |
15 | await I.sendPostRequest('https://httpbin.org/post', form);
16 | I.seeResponseCodeIsSuccessful();
17 | });
18 |
--------------------------------------------------------------------------------
/src/PUT_test.ts:
--------------------------------------------------------------------------------
1 | export {}
2 | const faker = require('faker');
3 | const { I } = inject();
4 | let id:any;
5 | let newUser:any;
6 |
7 | Feature('PUT tests');
8 |
9 | Before(async () => {
10 | newUser = await I.have('user', null, null);
11 | id = newUser.id;
12 | });
13 |
14 | Scenario('Verify creating new user', async () => {
15 | const newName = faker.name.firstName();
16 | newUser.name = newName;
17 | const res = await I.sendPutRequest(`/api/users/${id}`, newUser);
18 | await I.expectEqual(res.data.name, newName);
19 | });
20 |
--------------------------------------------------------------------------------
/src/fixtures/test_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kobenguyent/codeceptjs-rest-demo/5330bae48d7a22f8eae56ecc6103757f861c3c03/src/fixtures/test_image.png
--------------------------------------------------------------------------------
/stepObjects/custom.steps.ts:
--------------------------------------------------------------------------------
1 | import userSteps from './userSteps';
2 |
3 | const faker = require('faker');
4 | const FormData = require('form-data');
5 |
6 | export = () => {
7 | return actor({
8 | async createNewUser(userData?:object) {
9 | let payload = userData || {
10 | name: faker.name.firstName(),
11 | job: 'leader'
12 | };
13 |
14 | return this.sendPostRequest('/api/users', payload);
15 | },
16 | createFormData(key, value) {
17 | let form = new FormData();
18 | form.append(key, value);
19 | return form;
20 | },
21 | ...userSteps
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/stepObjects/userSteps.ts:
--------------------------------------------------------------------------------
1 | const userSteps = {
2 | async getUserPerPage(page = 2) {
3 | return this.sendGetRequest(`/api/users?page=${page}`);
4 | },
5 |
6 | async getUserById(id: number) {
7 | return this.sendGetRequest(`/api/users/${id}`);
8 | }
9 | }
10 |
11 | export default userSteps;
--------------------------------------------------------------------------------
/steps.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | type steps_file = typeof import('./stepObjects/custom.steps');
3 | type ExpectHelper = import('codeceptjs-expect');
4 |
5 | declare namespace CodeceptJS {
6 | interface SupportObject { I: I, current: any }
7 | interface Methods extends REST, JSONResponse, ExpectHelper, ApiDataFactory {}
8 | interface I extends ReturnType, WithTranslation, WithTranslation, WithTranslation {}
9 | namespace Translation {
10 | interface Actions {}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "files": true
4 | },
5 | "compilerOptions": {
6 | "target": "es2018",
7 | "lib": ["es2018", "DOM"],
8 | "esModuleInterop": true,
9 | "module": "commonjs",
10 | "strictNullChecks": false,
11 | "types": ["codeceptjs", "node"],
12 | "declaration": true,
13 | "skipLibCheck": true,
14 | "outDir": "./dist"
15 | },
16 | "exclude": ["node_modules", "**/custom.steps.ts"]
17 | }
18 |
--------------------------------------------------------------------------------