update: separate stateful and stateless scenarios
This commit is contained in:
parent
5d7a85b729
commit
97098482af
36
README.md
36
README.md
@ -1,6 +1,6 @@
|
|||||||
# cic-ussd-e2e
|
# cic-ussd-e2e
|
||||||
|
|
||||||
> Mocking user interaction with cic-ussd
|
Bunch of test scripts to automate traversing USSD menu's with little human interaction and as close replication to actual USSD usage including AT style reqests and waiting times.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@ -15,19 +15,39 @@
|
|||||||
|
|
||||||
- Node.js
|
- Node.js
|
||||||
- Install dependencies with `$ npm i`
|
- Install dependencies with `$ npm i`
|
||||||
|
- Set global config in `config.js`
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
|
||||||
|
| Test | Type | Description |
|
||||||
|
| -------------- | --------- | ---------------------------------------------- |
|
||||||
|
| initial_menu | Statless | Main menu > Help > Exit |
|
||||||
|
| display_sarafu | Stateless | Main menu > My Sarafu > Exit |
|
||||||
|
| reset_pin | Stateful | Main menu > Account options > Reset pin > Exit |
|
||||||
|
|
||||||
|
### Stateful scenarios
|
||||||
|
|
||||||
|
These tests live individually in their own folder. They require some ENV variables to be set before running them.
|
||||||
|
|
||||||
|
### Stateless scenarios
|
||||||
|
|
||||||
|
These are included under the `stateless` folder and can be run in any order and have multple tests under the same folder since they only check correctness of menu responses.
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm run test
|
# Set env variables for stateful scenarios, refer to individual test folders
|
||||||
|
$ npx tap --bail scenarios/SCENARIO_NAME
|
||||||
```
|
```
|
||||||
|
|
||||||
## Scenarios
|
Examples:
|
||||||
|
|
||||||
| Test | Description |
|
- `CURRENT_PIN=4444 NEW_PIN=3333 npx tap --bail scenarios/stateless`
|
||||||
| ---------------- | ---------------------------- |
|
|
||||||
| 1_initial_menu | Main menu > Help > Exit |
|
### Limitations
|
||||||
| 2_display_sarafu | Main menu > My Sarafu > Exit |
|
|
||||||
|
- These scripts cannot reset USSD sessions upon failure. You will need to manually set your account USSD menu to the main menu
|
||||||
|
- Cannot verify sms status, confirm with cic-notify for delivery
|
||||||
|
|
||||||
### Writing your own scenario
|
### Writing your own scenario
|
||||||
|
|
||||||
@ -35,6 +55,8 @@ $ npm run test
|
|||||||
- Control wait between menus with the `wait(time in ms)` function
|
- Control wait between menus with the `wait(time in ms)` function
|
||||||
- Follow any spec
|
- Follow any spec
|
||||||
- For any menu traversal, remember to append a `*` before the next input, this correctly mocks AT webhooks e.g.
|
- For any menu traversal, remember to append a `*` before the next input, this correctly mocks AT webhooks e.g.
|
||||||
|
- Dyanmic inputs for stateful scenarios must come from an env var that can be set again on next run
|
||||||
|
- Include a `tap-parallel-not-ok` file in any new folder
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Initial Main menu, no input
|
# Initial Main menu, no input
|
||||||
|
13
config.js
Normal file
13
config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
ussd: {
|
||||||
|
serviceCode: "*483*061#",
|
||||||
|
endpoint: "https://ussd.grassecon.net",
|
||||||
|
timeout: 3000,
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
waitNextMenu: 3000,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
ussdPhone: "254711777734",
|
||||||
|
},
|
||||||
|
};
|
14
lib.js
14
lib.js
@ -1,17 +1,19 @@
|
|||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const phin = require("phin");
|
const phin = require("phin");
|
||||||
|
|
||||||
async function ussdClient(sessionId, input = "") {
|
const conf = require("./config");
|
||||||
|
|
||||||
|
async function ussdClient(phone, sessionId, input = "") {
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
url: "https://ussd.grassecon.net",
|
url: conf.ussd.endpoint,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
parse: "string",
|
parse: "string",
|
||||||
timeout: 3000,
|
timeout: conf.ussd.timeout,
|
||||||
form: {
|
form: {
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
// Get from a config file, then pass as a param
|
// Get from a conf file, then pass as a param
|
||||||
phoneNumber: "254711777734",
|
phoneNumber: phone,
|
||||||
serviceCode: "*483*061#",
|
serviceCode: conf.ussd.serviceCode,
|
||||||
text: input,
|
text: input,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
113
scenarios/reset_pin/reset_pin.test.js
Normal file
113
scenarios/reset_pin/reset_pin.test.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
const test = require("tap").test;
|
||||||
|
const rangi = require("rangi");
|
||||||
|
|
||||||
|
const lib = require("../../lib");
|
||||||
|
const conf = require("../../config");
|
||||||
|
|
||||||
|
test("Reset Pin", async (t) => {
|
||||||
|
t.before((t) => {
|
||||||
|
if (!process.env.CURRENT_PIN || !process.env.NEW_PIN) {
|
||||||
|
t.bailout("ENV not set");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionId = lib.newSession();
|
||||||
|
const currentPin = process.env.CURRENT_PIN;
|
||||||
|
const newPin = process.env.NEW_PIN;
|
||||||
|
|
||||||
|
t.test("Display menu and Sarafu balance", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /Balance/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Go to My Accont menu", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3");
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /PIN options/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Go to Pin options", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3*5");
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /Change my PIN/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Start Pin Change", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3*5*1");
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /Enter current PIN/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Enter current Pin", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(
|
||||||
|
conf.user.ussdPhone,
|
||||||
|
sessionId,
|
||||||
|
`3*5*1*${currentPin}`
|
||||||
|
);
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /new four number PIN/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Enter new Pin", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(
|
||||||
|
conf.user.ussdPhone,
|
||||||
|
sessionId,
|
||||||
|
`3*5*1*${currentPin}*${newPin}`
|
||||||
|
);
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /new four number PIN again/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Enter new Pin agin", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(
|
||||||
|
conf.user.ussdPhone,
|
||||||
|
sessionId,
|
||||||
|
`3*5*1*${currentPin}*${newPin}*${newPin}`
|
||||||
|
);
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "CON");
|
||||||
|
t.match(r.text, /Your request has been sent/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test("Exit", async (t) => {
|
||||||
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
|
const r = await lib.ussdClient(
|
||||||
|
conf.user.ussdPhone,
|
||||||
|
sessionId,
|
||||||
|
`3*5*1*${currentPin}*${newPin}*${newPin}*99`
|
||||||
|
);
|
||||||
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
|
t.equal(r.code, "END");
|
||||||
|
t.match(r.text, /Thank/g);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
@ -1,16 +1,17 @@
|
|||||||
const test = require("tap").test;
|
const test = require("tap").test;
|
||||||
const rangi = require("rangi");
|
const rangi = require("rangi");
|
||||||
|
|
||||||
const lib = require("../lib");
|
const lib = require("../../lib");
|
||||||
|
const conf = require("../../config");
|
||||||
|
|
||||||
test("Initial menu, display balances, exit", async (t) => {
|
test("Display Sarafu", async (t) => {
|
||||||
const sessionId = lib.newSession();
|
const sessionId = lib.newSession();
|
||||||
|
|
||||||
t.plan(3);
|
t.plan(3);
|
||||||
|
|
||||||
t.test("Display menu and Sarafu balance", async (t) => {
|
t.test("Display menu and Sarafu balance", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId);
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "CON");
|
t.equal(r.code, "CON");
|
||||||
@ -19,8 +20,8 @@ test("Initial menu, display balances, exit", async (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
t.test("Go to My Sarafu menu", async (t) => {
|
t.test("Go to My Sarafu menu", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId, "2");
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "2");
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "CON");
|
t.equal(r.code, "CON");
|
||||||
@ -29,8 +30,8 @@ test("Initial menu, display balances, exit", async (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
t.test("Exit", async (t) => {
|
t.test("Exit", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId, "2*00");
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "2*00");
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "END");
|
t.equal(r.code, "END");
|
@ -1,16 +1,17 @@
|
|||||||
const test = require("tap").test;
|
const test = require("tap").test;
|
||||||
const rangi = require("rangi");
|
const rangi = require("rangi");
|
||||||
|
|
||||||
const lib = require("../lib");
|
const lib = require("../../lib");
|
||||||
|
const conf = require("../../config");
|
||||||
|
|
||||||
test("Initial menu, go to help, exit", async (t) => {
|
test("Initial Menu", async (t) => {
|
||||||
const sessionId = lib.newSession();
|
const sessionId = lib.newSession();
|
||||||
|
|
||||||
t.plan(3);
|
t.plan(3);
|
||||||
|
|
||||||
t.test("Display menu and Sarafu balance", async (t) => {
|
t.test("Display menu and Sarafu balance", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId);
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "CON");
|
t.equal(r.code, "CON");
|
||||||
@ -19,8 +20,8 @@ test("Initial menu, go to help, exit", async (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
t.test("Go to help menu", async (t) => {
|
t.test("Go to help menu", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId, "4");
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "4");
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "CON");
|
t.equal(r.code, "CON");
|
||||||
@ -29,8 +30,8 @@ test("Initial menu, go to help, exit", async (t) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
t.test("Exit", async (t) => {
|
t.test("Exit", async (t) => {
|
||||||
await lib.wait(3000);
|
await lib.wait(conf.test.waitNextMenu);
|
||||||
const r = await lib.ussdClient(sessionId, "4*99");
|
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "4*99");
|
||||||
console.log(rangi.cyan(r.text));
|
console.log(rangi.cyan(r.text));
|
||||||
|
|
||||||
t.equal(r.code, "END");
|
t.equal(r.code, "END");
|
0
scenarios/stateless/tap-parallel-not-ok
Normal file
0
scenarios/stateless/tap-parallel-not-ok
Normal file
@ -448,7 +448,7 @@ browserslist@^4.17.5:
|
|||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
|
||||||
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
|
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite "^1.0.30001317"
|
caniuse-lite "^1.0.config.test.waitNextMenu1317"
|
||||||
electron-to-chromium "^1.4.84"
|
electron-to-chromium "^1.4.84"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
node-releases "^2.0.2"
|
node-releases "^2.0.2"
|
||||||
@ -493,9 +493,9 @@ camelcase@^5.0.0, camelcase@^5.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001317:
|
caniuse-lite@^1.0.config.test.waitNextMenu1317:
|
||||||
version "1.0.30001320"
|
version "1.0.config.test.waitNextMenu1320"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.config.test.waitNextMenu1320.tgz#8397391bec389b8ccce328636499b7284ee13285"
|
||||||
integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==
|
integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==
|
||||||
|
|
||||||
cardinal@^2.1.1:
|
cardinal@^2.1.1:
|
||||||
|
Reference in New Issue
Block a user