├── .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 | [![Donate with PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/peternguyentr?country.x=DE&locale.x=en_US) 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/bb3182c5d5014093be06ffbd4bf7eb6f)](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)[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](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 | ![Screenshot 2023-08-09 at 10.30.37.png](assets%2FScreenshot%202023-08-09%20at%2010.30.37.png) 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 | --------------------------------------------------------------------------------