├── .github
└── workflows
│ ├── api.yml
│ └── web.yml
├── .gitignore
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── api
├── .mocharc.js
├── config
│ └── setup.ts
├── package-lock.json
├── package.json
├── resources
│ └── payloads.ts
├── services
│ └── endpoints.ts
├── test
│ └── reqres.ts
├── tsconfig.json
└── utils
│ ├── formatter.ts
│ └── httpCalls.ts
├── images
├── appium-logo.svg
├── setup.gif
└── wdio-logo.svg
├── mobile
├── README.md
├── app
│ └── android
│ │ └── ApiDemos-debug.apk
├── config
│ └── capabilities.ts
├── package-lock.json
├── package.json
├── pages
│ ├── base.page.ts
│ └── elements.page.ts
├── sample
│ ├── android_config.png
│ ├── appium_driver_list.png
│ └── report.png
├── specs
│ └── apiDemoApp.spec.ts
├── static
│ ├── constants.ts
│ └── pathconstants.ts
├── tsconfig.json
├── wdio.conf.parallel.ts
└── wdio.conf.ts
├── package-lock.json
├── package.json
├── start.js
└── web
├── .env.example
├── config
├── capabilities.ts
├── wdio.conf.docker.ts
├── wdio.conf.e2e.docker.ts
├── wdio.conf.e2e.ts
└── wdio.conf.ts
├── docker-compose.yml
├── generator
├── bddEmail.ts
├── emailBody.ts
├── index.ts
└── mochaEmail.ts
├── package-lock.json
├── package.json
├── pages
├── basePage.ts
├── form.page.ts
├── frameShadowDom.page.ts
├── login.page.ts
├── secure.page.ts
└── webTables.page.ts
├── resources
├── logindata.ts
└── testdata.json
├── static
├── frameworkConstants.ts
└── pathConstants.ts
├── tests
├── cucumber
│ ├── features
│ │ ├── BackgroundDatatable.feature
│ │ └── ExamplesTable.feature
│ └── steps
│ │ ├── BackgroundDatatable.steps..ts
│ │ └── ExamplesTable.steps.ts
├── mocha
│ ├── formElements.spec.ts
│ ├── frameShadowDom.spec.ts
│ ├── herokuAppLogin.spec.ts
│ └── webTables.spec.ts
└── smoke.spec.ts
├── tsconfig.json
├── types
├── customTypes.d.ts
├── external.d.ts
└── webelements.d.ts
└── utils
├── base64Utils.ts
├── envReader.ts
├── fileSystem.ts
└── mailer.ts
/.github/workflows/api.yml:
--------------------------------------------------------------------------------
1 | name: API CI
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 |
9 | ApiTest:
10 | runs-on: ubuntu-latest
11 | defaults:
12 | run:
13 | working-directory: ./api
14 | steps:
15 | - name: Checkout project
16 | uses: actions/checkout@v3
17 | - name: Setup Node.js
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 | cache: 'npm'
22 | cache-dependency-path: './api/package-lock.json'
23 | - run: npm install
24 | - run: npm run test
25 |
26 | - name: Generate API Mochawesome Report
27 | if: always()
28 | uses: actions/upload-artifact@v3
29 | with:
30 | name: APIMochaReport
31 | path: api/reports
--------------------------------------------------------------------------------
/.github/workflows/web.yml:
--------------------------------------------------------------------------------
1 | name: Web CI
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 |
9 | smoke:
10 | runs-on: ubuntu-latest
11 | defaults:
12 | run:
13 | working-directory: ./web
14 | steps:
15 | - name: Checkout project
16 | uses: actions/checkout@v3
17 | - name: Setup Node.js
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 | cache: 'npm'
22 | cache-dependency-path: './web/package-lock.json'
23 | - run: npm install --legacy-peer-deps
24 | - run: npm run smoke
25 |
26 | MochaTests:
27 | needs: [smoke]
28 | runs-on: ubuntu-latest
29 | defaults:
30 | run:
31 | working-directory: ./web
32 | steps:
33 | - name: Checkout project
34 | uses: actions/checkout@v3
35 | - name: Setup Node.js
36 | uses: actions/setup-node@v3
37 | with:
38 | node-version: 18
39 | cache: 'npm'
40 | cache-dependency-path: './web/package-lock.json'
41 | - run: npm install --legacy-peer-deps
42 | - run: npm run test
43 |
44 | - name: Generate Mochawesome Report
45 | if: always()
46 | run: npm run report:mocha:ci
47 |
48 | - name: Export Mochawesome Report
49 | if: always()
50 | uses: actions/upload-artifact@v3
51 | with:
52 | name: MochaHTMLReport
53 | path: web/mochawesome-report
54 |
55 | CucumberBDDTests:
56 | needs: [smoke]
57 | runs-on: ubuntu-latest
58 | defaults:
59 | run:
60 | working-directory: ./web
61 | steps:
62 | - name: Checkout project
63 | uses: actions/checkout@v3
64 | - name: Setup Node.js
65 | uses: actions/setup-node@v3
66 | with:
67 | node-version: 18
68 | cache: 'npm'
69 | cache-dependency-path: './web/package-lock.json'
70 | - run: npm install --legacy-peer-deps
71 | - run: npm run test:e2e
72 |
73 | - name: Generate Cucumber HTML Report
74 | if: always()
75 | run: npm run report:cucumber
76 |
77 | - name: Export Cucumber HTML Report
78 | if: always()
79 | uses: actions/upload-artifact@v3
80 | with:
81 | name: CucumberHTMLReport
82 | path: web/reports/cucumber/cucumber-report.html
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | api/node_modules/
2 | api/reports/
3 | mobile/node_modules/
4 | mobile/reports/
5 | web/node_modules/
6 | web/reports/
7 | web/mochawesome-report/
8 | web/tmp/
9 | web/.env
10 | notes.TODO
11 | node_modules/
12 | .DS_Store
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "akamud.vscode-theme-onedark",
4 | "alexkrechik.cucumberautocomplete",
5 | "pkief.material-icon-theme",
6 | "ms-azuretools.vscode-docker"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cucumberautocomplete.steps": [
3 | "./web/tests/cucumber/steps/*.ts"
4 | ],
5 | "cucumberautocomplete.strictGherkinCompletion": true,
6 | "editor.codeActionsOnSave": {
7 | "source.fixAll.tslint": "explicit",
8 | "source.organizeImports": "explicit"
9 | },
10 | "typescript.updateImportsOnFileMove.enabled": "always",
11 | "editor.formatOnSave": true
12 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 MD SADAB SAQIB
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Web, API and Mobile Test Automation Framework
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | [](https://github.com/sadabnepal/WebdriverIOTypeScriptE2E/actions/workflows/web.yml)
13 | [](https://github.com/sadabnepal/WebdriverIOTypeScriptE2E/actions/workflows/api.yml)
14 |
15 |
16 |
17 |
18 |
19 | #### Pre-requisite
20 | [](https://nodejs.org/en/download/)
21 | [](https://docs.docker.com/engine/install/)
22 | [](https://code.visualstudio.com/download)
23 | [](https://github.com/appium/appium-inspector/releases)
24 | [](https://developer.android.com/studio)
25 | [](https://www.azul.com/downloads/#zulu)
26 |
27 |
28 | #### Clone Repository
29 | ```bash
30 | git clone https://github.com/sadabnepal/web-mobile-api-test-framework.git
31 | cd web-mobile-api-test-framework
32 | ```
33 | -----
34 |
35 | ### Interactive CLI to run test:
36 | > Make sure mobile setup has been completed if selecting mobile as CLI option. See [Mobile Test](./mobile/README.md) for setup instructions. Before running actual test, presence of node_modules folder will be validated and if not not found installation will take place before proceeding any further.
37 | ```bash
38 | npm start
39 | ```
40 | It start wizard with test module options, based on user selection either of the below module will start locally or inside docker container. Code to control wizard and user selection is available in 'start.js' which is built using [enquirer](https://www.npmjs.com/package/enquirer) node package.
41 | Test Module Options : | UI | API | Mobile |
42 | 
43 |
44 |
45 | -----
46 |
47 | ### Web Test
48 | Install dependencies:
49 | > Navigate to "web" folder and then run below command
50 | ```bash
51 | npm install
52 | ```
53 |
54 | Setup .env file:
55 | create `.env` file inside web folder and update content with reference to `.env.example`
56 |
57 | Run test in local:
58 | > By default test will run in HEADLESS mode.
59 | > Update MODE=LOCAL in .env file to see test running in browser.
60 | ```bash
61 | npm test [ Mocha tests ]
62 | npm run test:e2e [ Cucumber BDD tests ]
63 | ```
64 |
65 | Run test in Docker:
66 | ```bash
67 | npm run test:docker [ Mocha tests]
68 | npm run test:e2e:docker [ Cucumber BDD tests ]
69 | ```
70 | > Pre and Post script will handle start and stop of docker containers automatically.
71 | > If containers does not stop automatically run "docker-compose down" command.
72 |
73 | Generate Report:
74 | ```bash
75 | npm run report:mocha
76 | npm run report:cucumber
77 | ```
78 |
79 | Report Paths:
80 | ```bash
81 | mocha: web/mochawesome-report/mochawesome-report.html
82 | cucumber: web/reports/cucumber/cucumber-report.html
83 | ```
84 |
85 | Send Report:
86 | > Update .env file details with reference of .env.example file
87 | ```bash
88 | npm run mailCucumberReport
89 | npm run mailMochaResult
90 | ```
91 | -----
92 |
93 | ### API Test
94 | Install dependencies:
95 | > Navigate to "api" folder and then run below command
96 | ```bash
97 | npm install
98 | ```
99 |
100 | Run test:
101 | ```bash
102 | npm test
103 | ```
104 |
105 | Report Paths:
106 | ```bash
107 | api/reports/mochawesome.html
108 | ```
109 |
110 | -----
111 |
112 | ### Mobile Test
113 |
114 | Appium setup: [Click here to open Appium SetUp README](/mobile/README.md)
115 |
116 | Install dependencies:
117 | > Navigate to "mobile" folder and then run below command
118 | ```bash
119 | npm install
120 | ```
121 |
122 | Run in local:
123 | > Make sure android virtual device is up and running before starting mobile test.
124 | ```bash
125 | npm run test [ Mobile tests ]
126 | ```
127 |
128 | Generate Report:
129 | ```bash
130 | npm run report
131 | ```
132 |
133 | Report Paths:
134 | ```bash
135 | mobile: mobile/reports/mobile.html
136 | ```
137 |
138 | -----
139 |
140 | #### Features:
141 | - Web, Mobile and API Testing
142 | - Mocha and Cucumber BDD framework
143 | - Page Object Design pattern
144 | - Docker with VNC integration
145 | - Parallel execution
146 | - Cross browser testing
147 | - Retry failed test
148 | - Screenshot in report for failed tests
149 | - Github actions
150 | - Send test report to list of Gmail
151 | - Use of types for method params optimization
152 | - Improved import statement using tsconfig path
153 |
154 | #### Tech stacks:
155 | [](https://webdriver.io/)
156 | [](https://www.typescriptlang.org/)
157 | [](https://mochajs.org/)
158 | [](https://cucumber.io/)
159 | [](https://www.chaijs.com/)
160 | [](https://github.com/visionmedia/supertest)
161 | [](https://github.com/enquirer/enquirer)
162 | [](https://www.docker.com/)
163 | [](https://github.com/appium/appium)
164 | [](https://github.com/appium/appium)
165 | [](https://github.com/nodemailer/nodemailer)
166 |
167 | #### Folder Structure:
168 | 
169 |
170 | #### Sample Email Report:
171 | 
172 |
--------------------------------------------------------------------------------
/api/.mocharc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | spec: ['test/**/*.ts'],
3 | package: './package.json',
4 | require: ['ts-node/register', 'tsconfig-paths/register'],
5 | extension: ['ts'],
6 | timeout: 20 * 1000,
7 | grep: '',
8 | ignore: [''],
9 | reporter: 'mochawesome',
10 | 'reporter-option': [
11 | 'reportDir=reports',
12 | 'reportFilename=index',
13 | 'reportTitle=API Test Report',
14 | 'charts=true',
15 | 'code=false',
16 | 'inline=true',
17 | 'autoOpen=false',
18 | 'showPassed=true',
19 | 'showFailed=true',
20 | 'showPending=true',
21 | 'showSkipped=true',
22 | 'showHooks=failed'
23 | ]
24 | };
--------------------------------------------------------------------------------
/api/config/setup.ts:
--------------------------------------------------------------------------------
1 | export const REQ_RES_BASE_URI = "https://reqres.in"
--------------------------------------------------------------------------------
/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "@faker-js/faker": "^6.3.1",
14 | "@types/chai": "^4.3.1",
15 | "@types/mocha": "^9.1.1",
16 | "@types/mochawesome": "^6.2.1",
17 | "@types/supertest": "^2.0.12",
18 | "chai": "^4.3.6",
19 | "dotenv": "^16.0.0",
20 | "mocha": "^10.0.0",
21 | "mochawesome": "^7.1.3",
22 | "supertest": "^6.2.3",
23 | "ts-node": "^10.7.0",
24 | "tsconfig-paths": "^4.0.0",
25 | "typescript": "^4.6.4"
26 | }
27 | }
--------------------------------------------------------------------------------
/api/resources/payloads.ts:
--------------------------------------------------------------------------------
1 | import { faker } from "@faker-js/faker"
2 |
3 | export const createUserPayload = {
4 | "name": faker.name.firstName() + " " + faker.name.lastName(),
5 | "job": faker.name.jobTitle()
6 | }
--------------------------------------------------------------------------------
/api/services/endpoints.ts:
--------------------------------------------------------------------------------
1 | export enum endpoints {
2 | USERS_SERVICE = "/api/users",
3 | USER_BY_ID_SERVICE = "/api/users/%s"
4 | }
--------------------------------------------------------------------------------
/api/test/reqres.ts:
--------------------------------------------------------------------------------
1 | import { assert } from 'chai';
2 | import { createUserPayload } from "resources/payloads";
3 | import { endpoints } from "services/endpoints";
4 | import { logResponseToMochaReport, stringFormatter } from 'utils/formatter';
5 | import { makeDELETECall, makeGETCall, makePOSTCall } from 'utils/httpCalls';
6 |
7 | describe('REQ RES users api validation', () => {
8 |
9 | it('should verify POST user call', async function () {
10 | const response = await makePOSTCall(endpoints.USERS_SERVICE, createUserPayload)
11 | logResponseToMochaReport(this, response);
12 | assert.equal(response.statusCode, 201)
13 | assert.equal(response.body.name, createUserPayload.name)
14 | assert.equal(response.body.job, createUserPayload.job)
15 | });
16 |
17 | it('should verify GET user/{id} call', async function () {
18 | const userByID = stringFormatter(endpoints.USER_BY_ID_SERVICE, 2)
19 | const response = await makeGETCall(userByID)
20 | logResponseToMochaReport(this, response);
21 | assert.equal(response.statusCode, 200)
22 | assert.equal(response.body.data.id, 2)
23 | });
24 |
25 | it('should verify DELETE user/{id} call', async function () {
26 | const userByID = stringFormatter(endpoints.USER_BY_ID_SERVICE, 2)
27 | const response = await makeDELETECall(userByID)
28 | logResponseToMochaReport(this, response);
29 | assert.equal(response.statusCode, 204)
30 | });
31 | });
--------------------------------------------------------------------------------
/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "esModuleInterop": true,
5 | "resolveJsonModule": true
6 | }
7 | }
--------------------------------------------------------------------------------
/api/utils/formatter.ts:
--------------------------------------------------------------------------------
1 | import addContext from 'mochawesome/addContext';
2 | import supertest from "supertest";
3 | import util from "util";
4 |
5 | export const stringFormatter = (data: string, value: string | number) => {
6 | return util.format(data, value)
7 | }
8 |
9 | const formatResponse = (response: supertest.Response) => {
10 | return `Response: ${JSON.stringify(response.body, null, 4)}`;
11 | }
12 |
13 | export const logResponseToMochaReport = (context: Mocha.Context, response: supertest.Response) => {
14 | addContext(context, formatResponse(response))
15 | }
--------------------------------------------------------------------------------
/api/utils/httpCalls.ts:
--------------------------------------------------------------------------------
1 | import { REQ_RES_BASE_URI } from 'config/setup';
2 | import { endpoints } from "services/endpoints";
3 | import supertest, { Response } from "supertest";
4 |
5 | const request = supertest(REQ_RES_BASE_URI)
6 |
7 | export const makeGETCall = async (endpoint: endpoints | string, payload?: object, headersAPI?: Record): Promise => {
8 | if (payload && headersAPI) return request.get(endpoint).set(headersAPI).send(payload);
9 | else if (payload) return request.get(endpoint).send(payload);
10 | else return request.get(endpoint);
11 | }
12 |
13 | export const makePOSTCall = async (endpoint: endpoints | string, payload: string | object, headers?: Record): Promise => {
14 | if (headers) return request.post(endpoint).set(headers);
15 | return request.post(endpoint).send(payload);
16 | }
17 |
18 | export const makeDELETECall = async (endpoint: endpoints | string, payload?: object): Promise => {
19 | if (payload) return request.delete(endpoint).send(payload);
20 | return request.delete(endpoint);
21 | }
--------------------------------------------------------------------------------
/images/appium-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/images/setup.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadabnepal/web-mobile-api-test-framework/d7e71af4527b003647890678cdd4eb62aee636de/images/setup.gif
--------------------------------------------------------------------------------
/images/wdio-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile/README.md:
--------------------------------------------------------------------------------
1 | #### Install Appium Server
2 | ```
3 | npm install -g appium [ install appium CLI version ]
4 | npm install -g appium-doctor [ install appium doctor ]
5 | appium --version [ To check appium version ]
6 | ```
7 |
8 | #### Verify drivers
9 | ```
10 | appium driver list [ To check available drivers ]
11 | appium driver install uiautomator2 [ install android driver]
12 | appium driver install xcuitest [ install ios driver]
13 | ```
14 |
15 | #### Setup Android SDK path environment variable
16 | ```
17 | - ANDROID_HOME =
18 | - %ANDROID_HOME%\tools [path variable]
19 | - %ANDROID_HOME%\tools\bin [path variable]
20 | - %ANDROID_HOME%\platform-tools [path variable]
21 | ```
22 |
23 | #### Setup/Create virtual device on Android studio:
24 | ```
25 | 1] Open Android Studio
26 | 2] Click on More Actions
27 | --> AVD Manager
28 | --> Create Virtual Device
29 | --> Select the device and OS version [ Refer Device Configurations ]
30 | --> Finish
31 | 3] Once Virtual device is created, click on Launch this AVD in the emulator.
32 | 4] Command to view the list of devices attached `adb devices`
33 | ```
34 |
35 | Device Configurations:
36 | ```
37 | Device 1: Pixel 3 - version 11
38 | Device 2: Nexus 6 - version 10 [ if you want to run tests in parallel ]
39 | ```
40 |
41 |
42 | #### Verify all setup
43 | ```
44 | appium-doctor --android [ To check Android set up ]
45 | appium-doctor --ios [ To check ios set up ]
46 | ```
47 | all options should be green checked as shown in below image to start.
48 | 
49 |
50 | [Go Back to main README](../../README.md)
51 |
--------------------------------------------------------------------------------
/mobile/app/android/ApiDemos-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadabnepal/web-mobile-api-test-framework/d7e71af4527b003647890678cdd4eb62aee636de/mobile/app/android/ApiDemos-debug.apk
--------------------------------------------------------------------------------
/mobile/config/capabilities.ts:
--------------------------------------------------------------------------------
1 | import { ANDROID_APP_PATH } from "../static/pathConstants";
2 |
3 | export const androidDeviceCapabilities = [
4 | {
5 | platformName: "Android",
6 | "appium:platformVersion": "11",
7 | "appium:deviceName": "Pixel 3",
8 | "appium:systemPort": 8200,
9 | "appium:automationName": "UiAutomator2",
10 | "appium:app": ANDROID_APP_PATH,
11 | 'appium:noReset': false,
12 | 'appium:newCommandTimeout': 30,
13 | "appium:autoGrantPermissions": true,
14 | "appium:avd": "Pixel_3",
15 | "appium:avdLaunchTimeout": 180000
16 | }
17 | ]
18 |
19 | export const androidMultiDeviceCapabilities = [
20 | ...androidDeviceCapabilities,
21 | {
22 | platformName: "Android",
23 | "appium:platformVersion": "10",
24 | "appium:deviceName": "Nexus 6",
25 | "appium:systemPort": 8201,
26 | "appium:automationName": "UiAutomator2",
27 | "appium:app": ANDROID_APP_PATH,
28 | 'appium:noReset': false,
29 | 'appium:newCommandTimeout': 30,
30 | "appium:autoGrantPermissions": true,
31 | "appium:avd": "Pixel_3",
32 | "appium:avdLaunchTimeout": 180000
33 | }
34 | ]
--------------------------------------------------------------------------------
/mobile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mobile",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "wdio run wdio.conf.ts",
8 | "test:parallel": "wdio run wdio.conf.parallel.ts",
9 | "report": "marge ./reports/wdio-ma-merged.json --reportTitle 'AppiumReport' --reportDir=./reports/ && move ./reports.html ./reports"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@wdio/appium-service": "^8.36.0",
16 | "@wdio/cli": "^8.36.0",
17 | "@wdio/json-reporter": "^8.36.0",
18 | "@wdio/local-runner": "^8.36.0",
19 | "@wdio/mocha-framework": "^8.36.0",
20 | "@wdio/spec-reporter": "^8.36.0",
21 | "mochawesome-report-generator": "^6.2.0",
22 | "ts-node": "^10.9.2",
23 | "typescript": "^5.4.5"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/mobile/pages/base.page.ts:
--------------------------------------------------------------------------------
1 | import { APP_PACKAGE } from "../static/constants";
2 |
3 | export default class BasePage {
4 |
5 | findByTextContains(partialText: string) {
6 | return $(`android=new UiSelector().textContains("${partialText}")`);
7 | }
8 |
9 | async scrollAndClickByText(text: string) {
10 | await $(`android=new UiScrollable(new UiSelector()).scrollTextIntoView("${text}")`).click();
11 | }
12 |
13 | async scrollHorizontally() {
14 | await $('android=new UiScrollable(new UiSelector()).setAsHorizontalList().scrollForward(2)');
15 | }
16 |
17 | async openUsingPackage(packageName: string) {
18 | await driver.startActivity(APP_PACKAGE, packageName)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/mobile/pages/elements.page.ts:
--------------------------------------------------------------------------------
1 | import BasePage from "./base.page"
2 |
3 | class PageElements extends BasePage {
4 | get appNameHeader() { return $('android.widget.TextView') }
5 | get allMenuItemsElements() { return $$(".android.widget.TextView") }
6 | get appMenuElement() { return $('~App') }
7 | get viewsMenuElement() { return $('~Views') }
8 | get listDialogueElement() { return $("//*[@content-desc='List dialog']") }
9 | get commandTwoElement() { return $("//*[@text='Command two']") }
10 | get commandTwoMsgElement() { return $('//*[@resource-id="android:id/message"]') }
11 | get okCancelElement() { return $('~OK Cancel dialog with a message') }
12 | get alertTitleElement() { return $('//*[@resource-id="android:id/alertTitle"]') }
13 | get actionBar() { return $('~Action Bar') }
14 | get activitySubMenu() { return $('~Activity') }
15 | get countryInputElement() { return $('//*[@resource-id="io.appium.android.apis:id/edit"]') }
16 | get dateWidgetMenu() { return $('~Date Widgets') }
17 | get dialogOption() { return $('~1. Dialog') }
18 | get dateElement() { return $('//*[@resource-id="io.appium.android.apis:id/dateDisplay"]'); }
19 | get changeDateButton() { return $('~change the date') }
20 | get dateOKButton() { return $('//android.widget.Button[@text="OK"]') }
21 | get wallpaperTextElement() { return $('//*[@resource-id="io.appium.android.apis:id/text"]') }
22 |
23 | async openMainMenu() {
24 | await this.openUsingPackage(".ApiDemos")
25 | }
26 |
27 | async openAlertPage() {
28 | await this.openUsingPackage(".app.AlertDialogSamples")
29 | }
30 |
31 | async openCountryInputPage() {
32 | await this.openUsingPackage(".view.AutoComplete1")
33 | }
34 |
35 | async openGalleryPage() {
36 | await this.openUsingPackage(".view.Gallery1")
37 | }
38 |
39 | async clickOnAppMenu() {
40 | await this.appMenuElement.click();
41 | }
42 |
43 | async clickOnViewsMenu() {
44 | await this.viewsMenuElement.click()
45 | }
46 |
47 | async navigateToCommandTwoPopup() {
48 | await this.openAlertPage()
49 | await this.listDialogueElement.click();
50 | await this.commandTwoElement.click();
51 | }
52 |
53 | async clickOnOkCancelDialogue() {
54 | await this.okCancelElement.click();
55 | }
56 |
57 | async clickOnActivityMenu() {
58 | await this.activitySubMenu.click()
59 | }
60 |
61 | async selectDay(day: string) {
62 | await $(`//android.view.View[@text='${day}']`).click()
63 | }
64 |
65 | async openDateDialogueMenu() {
66 | await this.dateWidgetMenu.click()
67 | await this.dialogOption.click()
68 | }
69 |
70 | async scrollToNextMonthAndSelectDay(day: string) {
71 | await this.scrollHorizontally()
72 | await this.selectDay(day)
73 | await this.dateOKButton.click()
74 | }
75 |
76 | async scrollGalleryHorizontally() {
77 | await this.scrollHorizontally()
78 | }
79 |
80 | async scrollAndClickOnWallpaperMenu() {
81 | await this.scrollAndClickByText('Wallpaper')
82 | }
83 |
84 |
85 | }
86 | export default new PageElements()
--------------------------------------------------------------------------------
/mobile/sample/android_config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadabnepal/web-mobile-api-test-framework/d7e71af4527b003647890678cdd4eb62aee636de/mobile/sample/android_config.png
--------------------------------------------------------------------------------
/mobile/sample/appium_driver_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadabnepal/web-mobile-api-test-framework/d7e71af4527b003647890678cdd4eb62aee636de/mobile/sample/appium_driver_list.png
--------------------------------------------------------------------------------
/mobile/sample/report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadabnepal/web-mobile-api-test-framework/d7e71af4527b003647890678cdd4eb62aee636de/mobile/sample/report.png
--------------------------------------------------------------------------------
/mobile/specs/apiDemoApp.spec.ts:
--------------------------------------------------------------------------------
1 | import elementsPage from '../pages/elements.page';
2 | import * as constants from '../static/constants';
3 |
4 | describe('API Demo Android APP tests', () => {
5 |
6 | it('should validate app name', async () => {
7 | await expect(elementsPage.appNameHeader).toHaveText(constants.APP_HEADER);
8 | })
9 |
10 | it('should validate all menu items', async () => {
11 | const actualMenuItems = await elementsPage.allMenuItemsElements.map(async menuItem => menuItem.getText());
12 | expect(actualMenuItems).toEqual(constants.MENU_ITEMS)
13 | expect(await elementsPage.allMenuItemsElements.length).toBeGreaterThan(0);
14 | })
15 |
16 | it('should open Action bar menu item', async () => {
17 | await elementsPage.clickOnAppMenu();
18 | await expect(elementsPage.actionBar).toBeExisting();
19 | })
20 |
21 | it('should validate command two menu with app activity', async () => {
22 | await elementsPage.navigateToCommandTwoPopup()
23 | await expect(elementsPage.commandTwoMsgElement).toHaveText(constants.COMMAND_TWO_POPUP_MSG);
24 | })
25 |
26 | it('should validate screen top send keys', async () => {
27 | await elementsPage.openCountryInputPage()
28 | await elementsPage.countryInputElement.setValue('Nepal')
29 | await expect(elementsPage.countryInputElement).toHaveText('Nepal')
30 | })
31 |
32 | it('should validate alert text and accept alert', async () => {
33 | await elementsPage.openAlertPage()
34 | await elementsPage.clickOnOkCancelDialogue()
35 | expect(await driver.getAlertText()).toEqual(constants.ALERT_TEXT)
36 | await driver.acceptAlert()
37 | await expect(elementsPage.alertTitleElement).not.toExist()
38 | })
39 |
40 | it('should validate alert text and dismiss alert', async () => {
41 | await elementsPage.openAlertPage()
42 | await elementsPage.clickOnOkCancelDialogue()
43 | expect(await driver.getAlertText()).toEqual(constants.ALERT_TEXT)
44 | await driver.dismissAlert()
45 | await expect(elementsPage.alertTitleElement).not.toExist()
46 | })
47 |
48 | it('should validate vertical scrolling', async () => {
49 | await elementsPage.openMainMenu()
50 | await elementsPage.clickOnAppMenu()
51 | await elementsPage.clickOnActivityMenu()
52 | await elementsPage.scrollAndClickOnWallpaperMenu()
53 | await expect(elementsPage.wallpaperTextElement).toHaveText(expect.stringContaining(constants.WALLPAPER_TEXT));
54 | })
55 |
56 | it('should validate horizontal scrolling', async () => {
57 | await elementsPage.openGalleryPage()
58 | await elementsPage.scrollGalleryHorizontally()
59 | })
60 |
61 | it('should validate next month date selection using scroll', async () => {
62 | await elementsPage.openMainMenu()
63 | await elementsPage.clickOnViewsMenu()
64 | await elementsPage.openDateDialogueMenu()
65 | const currentDate = await elementsPage.dateElement.getText()
66 | await elementsPage.changeDateButton.click()
67 | await elementsPage.scrollToNextMonthAndSelectDay('10')
68 | const updateDate = await elementsPage.dateElement.getText()
69 | expect(updateDate).not.toEqual(currentDate)
70 | })
71 |
72 | })
--------------------------------------------------------------------------------
/mobile/static/constants.ts:
--------------------------------------------------------------------------------
1 | export const APP_PACKAGE = "io.appium.android.apis";
2 | export const APP_HEADER = "API Demos";
3 | export const COMMAND_TWO_POPUP_MSG = "You selected: 1 , Command two";
4 | export const MENU_ITEMS = ['API Demos', 'Accessibility', 'Animation', 'App', 'Content', 'Graphics', 'Media', 'NFC', 'OS', 'Preference', 'Text', 'Views'];
5 | export const ALERT_TEXT = `Lorem ipsum dolor sit aie consectetur adipiscing
6 | Plloaso mako nuto siwuf cakso dodtos anr koop.`;
7 | export const WALLPAPER_TEXT = 'Example of how you can make an activity have a translucent background';
--------------------------------------------------------------------------------
/mobile/static/pathconstants.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 |
3 | export const ANDROID_APP_PATH = join(process.cwd(), 'app', 'android', 'ApiDemos-debug.apk')
4 | export const JSON_OUTPUT_DIR = join(process.cwd(), 'reports');
--------------------------------------------------------------------------------
/mobile/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "resolveJsonModule": true,
5 | "target": "es2019",
6 | "module": "commonjs",
7 | "strict": true,
8 | "noImplicitAny": false,
9 | "types": [
10 | "node",
11 | "@wdio/globals/types",
12 | "@wdio/mocha-framework",
13 | "expect-webdriverio",
14 | "@wdio/appium-service"
15 | ],
16 | "allowSyntheticDefaultImports": true,
17 | "esModuleInterop": true,
18 | "experimentalDecorators": true,
19 | "emitDecoratorMetadata": true
20 | }
21 | }
--------------------------------------------------------------------------------
/mobile/wdio.conf.parallel.ts:
--------------------------------------------------------------------------------
1 | import type { Options } from '@wdio/types';
2 | import { androidMultiDeviceCapabilities } from './config/capabilities';
3 | import { config as baseConfig } from './wdio.conf';
4 |
5 | export const config: Options.Testrunner = {
6 | ...baseConfig,
7 | capabilities: androidMultiDeviceCapabilities,
8 | }
--------------------------------------------------------------------------------
/mobile/wdio.conf.ts:
--------------------------------------------------------------------------------
1 | import type { Options } from '@wdio/types';
2 | import { androidDeviceCapabilities } from './config/capabilities';
3 | import { JSON_OUTPUT_DIR } from './static/pathConstants';
4 |
5 | export const config: Options.Testrunner = {
6 | // =====================
7 | // ts-node Configurations
8 | // =====================
9 | autoCompileOpts: {
10 | autoCompile: true,
11 | tsNodeOpts: {
12 | transpileOnly: true,
13 | project: './tsconfig.json'
14 | }
15 | },
16 | // ====================
17 | // Runner Configuration
18 | // ====================
19 | port: 4723,
20 | // ==================
21 | // Specify Test Files
22 | // ==================
23 | specs: [
24 | './specs/**/*.ts'
25 | ],
26 | exclude: [
27 | // 'path/to/excluded/files'
28 | ],
29 | // ============
30 | // Capabilities
31 | // ============
32 | maxInstances: 10,
33 | capabilities: androidDeviceCapabilities,
34 | // ===================
35 | // Test Configurations
36 | // ===================
37 | // Level of logging verbosity: trace | debug | info | warn | error | silent
38 | logLevel: 'info',
39 | bail: 0,
40 | baseUrl: 'http://localhost',
41 | waitforTimeout: 10000,
42 | connectionRetryTimeout: 120000,
43 | connectionRetryCount: 3,
44 | services: ['appium'],
45 | framework: 'mocha',
46 | // specFileRetries: 1,
47 | // specFileRetriesDelay: 0,
48 | reporters: ['spec',
49 | ['json', {
50 | outputDir: JSON_OUTPUT_DIR,
51 | outputFileFormat: (opts: any) => {
52 | return `results-${opts.cid}.${opts.capabilities.platformName}.json`
53 | }
54 | }]],
55 | mochaOpts: {
56 | compilers: [],
57 | ui: 'bdd',
58 | timeout: 60000
59 | },
60 | //
61 | // =====
62 | // Hooks
63 | // =====
64 | // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
65 | // it and to build services around it. You can either apply a single function or an array of
66 | // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
67 | // resolved to continue.
68 | /**
69 | * Gets executed once before all workers get launched.
70 | * @param {Object} config wdio configuration object
71 | * @param {Array.