Skip to content

Commit 0097fcc

Browse files
author
James Fuqian
authored
Merge pull request #29 from CMSgov/jfuqian/BB2-1596-Add-selenium-test-for-node-sample-app
[BB2-1596] Add selenium tests for node sample app
2 parents acbbdf0 + 8b45a6a commit 0097fcc

13 files changed

Lines changed: 10450 additions & 78 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ copy server/src/configs/sample.config.ts -> server/src/configs/config.ts
8686
yarn --cwd server install
8787
yarn --cwd server test
8888

89+
## Run selenium tests in docker
90+
91+
Configure the remote target BB2 instance where the tested app is registered (as described above "Running the Back-end & Front-end")
92+
93+
Go to local repo base directory, from there run:
94+
95+
docker-compose -f docker-compose.selenium.yml up --abort-on-container-exit
96+
97+
Note: --abort-on-container-exit will abort client and server containers when selenium tests ends
98+
99+
## Visual trouble shoot
100+
101+
Install VNC viewer and point browser to http://localhost:5900 to monitor web UI interactions
102+
89103
## Error Responses and handling:
90104

91105
[See ErrorResponses.md](./ErrorResponses.md)

client/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:14.17.1
1+
FROM node:16.17.1
22

33
LABEL version="1.0"
44
LABEL description="Demo of a Medicare claims data sample app"

client/package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
"@types/react-dom": "^17.0.0",
1111
"axios": "^0.21.2",
1212
"http-proxy-middleware": "^1.3.1",
13-
"react": "^17.0.2",
14-
"react-dom": "^17.0.2",
15-
"react-router-dom": "^5.2.0",
16-
"react-scripts": "4.0.3",
17-
"sass": "^1.49.7",
18-
"typescript": "^4.1.2",
19-
"web-vitals": "^1.0.1"
13+
"react": "^18.2.0",
14+
"react-dom": "^18.2.0",
15+
"react-router-dom": "^6.4.2",
16+
"react-scripts": "5.0.1",
17+
"node-sass": "7.0.3",
18+
"typescript": "^4.8.4",
19+
"web-vitals": "^3.0.3"
2020
},
2121
"scripts": {
2222
"start": "REACT_APP_CTX=docker react-scripts start",
@@ -45,11 +45,11 @@
4545
]
4646
},
4747
"devDependencies": {
48-
"@testing-library/jest-dom": "^5.16.2",
49-
"@testing-library/react": "^12.1.3",
48+
"@testing-library/jest-dom": "^5.16.5",
49+
"@testing-library/react": "^13.4.0",
5050
"@testing-library/user-event": "^13.5.0",
51-
"@types/jest": "^27.4.0",
52-
"@types/react-router-dom": "^5.1.7",
51+
"@types/jest": "^29.1.2",
52+
"@types/react-router-dom": "^5.3.3",
5353
"@typescript-eslint/eslint-plugin": "^5.12.0",
5454
"@typescript-eslint/parser": "^5.12.0",
5555
"eslint-config-react-app": "^7.0.0",

client/src/components/patientData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default function PatientData() {
3434
<div>
3535
<h4>{ header }</h4>
3636
</div>
37-
<Button variation="primary" onClick={goAuthorize}>Authorize</Button>
37+
<Button id="auth_btn" variation="primary" onClick={goAuthorize}>Authorize</Button>
3838
</div>
3939
</div>
4040
);

client/yarn.lock

Lines changed: 10219 additions & 0 deletions
Large diffs are not rendered by default.

docker-compose.selenium.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
version: '3'
2+
3+
services:
4+
server:
5+
build:
6+
context: ./server
7+
dockerfile: ./Dockerfile
8+
environment:
9+
- SELENIUM_TESTS=true
10+
- DANGEROUSLY_DISABLE_HOST_CHECK=true
11+
ports:
12+
- "3001:3001"
13+
- "9229:9229"
14+
client:
15+
build:
16+
context: ./client
17+
dockerfile: ./Dockerfile
18+
environment:
19+
- SELENIUM_TESTS=true
20+
- DANGEROUSLY_DISABLE_HOST_CHECK=true
21+
ports:
22+
- "3000:3000"
23+
selenium-tests:
24+
build:
25+
context: ./selenium_tests
26+
dockerfile: ./Dockerfile
27+
command: pytest ./src/test_node_sample.py
28+
depends_on:
29+
- chrome
30+
- server
31+
- client
32+
chrome:
33+
image: selenium/standalone-chrome-debug
34+
hostname: chrome
35+
ports:
36+
- "4444:4444"
37+
- "5900:5900"

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ services:
1313
context: ./client
1414
dockerfile: ./Dockerfile
1515
ports:
16-
- "3000:3000"
16+
- "3000:3000"

selenium_tests/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM selenium/standalone-chrome-debug
2+
3+
ENV PYTHONUNBUFFERED 1
4+
USER root
5+
RUN apt-get update && apt-get install -yq python3.8 python3-pip
6+
RUN mkdir /code
7+
ADD . /code/
8+
WORKDIR /code
9+
RUN ln -s /usr/bin/python3 /usr/local/bin/python
10+
RUN pip3 install --upgrade pip
11+
RUN pip3 install selenium pytest
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Generated by Selenium IDE
2+
import time
3+
from selenium import webdriver
4+
from selenium.webdriver.common.by import By
5+
from selenium.webdriver.support import expected_conditions as EC
6+
from selenium.webdriver.support.wait import WebDriverWait
7+
8+
9+
class TestNodeSampleApp():
10+
driver_ready = False
11+
12+
def setup_method(self, method):
13+
if not TestNodeSampleApp.driver_ready:
14+
time.sleep(20)
15+
TestNodeSampleApp.driver_ready = True
16+
print("set driver_ready={}".format(TestNodeSampleApp.driver_ready))
17+
else:
18+
print("driver_ready={}".format(TestNodeSampleApp.driver_ready))
19+
20+
opt = webdriver.ChromeOptions()
21+
opt.add_argument("--disable-dev-shm-usage")
22+
opt.add_argument("--disable-web-security")
23+
opt.add_argument("--allow-running-insecure-content")
24+
opt.add_argument("--no-sandbox")
25+
opt.add_argument("--disable-setuid-sandbox")
26+
opt.add_argument("--disable-webgl")
27+
opt.add_argument("--disable-popup-blocking")
28+
opt.add_argument("--enable-javascript")
29+
opt.add_argument('--allow-insecure-localhost')
30+
opt.add_argument('--window-size=1920,1080')
31+
opt.add_argument("--whitelisted-ips=''")
32+
33+
# opt.add_argument('--headless')
34+
# self.driver = webdriver.Chrome(options=opt)
35+
self.driver = webdriver.Remote(
36+
command_executor='http://chrome:4444/wd/hub', options=opt)
37+
38+
def teardown_method(self, method):
39+
self.driver.quit()
40+
41+
def _find_elem_xpath(self, xpath_expr, **kwargs):
42+
elems = self.driver.find_elements(By.XPATH, xpath_expr)
43+
assert elems is not None
44+
return elems
45+
46+
def _find_and_click(self, timeout_sec, by, by_expr, **kwargs):
47+
elem = WebDriverWait(self.driver, timeout_sec).until(
48+
EC.visibility_of_element_located((by, by_expr)))
49+
assert elem is not None
50+
elem.click()
51+
return elem
52+
53+
def _find_and_return(self, timeout_sec, by, by_expr, **kwargs):
54+
elem = WebDriverWait(self.driver, timeout_sec).until(
55+
EC.visibility_of_element_located((by, by_expr)))
56+
assert elem is not None
57+
return elem
58+
59+
def _find_and_sendkey(self, timeout_sec, by, by_expr, txt, **kwargs):
60+
elem = WebDriverWait(self.driver, timeout_sec).until(
61+
EC.visibility_of_element_located((by, by_expr)))
62+
assert elem is not None
63+
elem.send_keys(txt)
64+
return elem
65+
66+
def _assert_EOB_table_header_present(self):
67+
self._find_and_return(30, By.ID, "column_1")
68+
self._find_and_return(30, By.ID, "column_2")
69+
self._find_and_return(30, By.ID, "column_3")
70+
71+
def _assert_EOB_table_records_present(self, cnt):
72+
xpath = "//table/tbody/tr/td[@data-title='NDC Code']"
73+
elements = self._find_elem_xpath(xpath)
74+
assert len(elements) == cnt
75+
76+
def _input_user_and_passwd_and_login(self):
77+
self._find_and_sendkey(30, By.ID, "username-textbox", "BBUser10000")
78+
self._find_and_sendkey(30, By.ID, "password-textbox", "PW10000!")
79+
self._find_and_click(30, By.ID, "login-button")
80+
81+
def test_node_sample_app_grant_access(self):
82+
self.driver.get("http://client:3000/")
83+
self.driver.set_window_size(1500, 1800)
84+
elem = self._find_and_click(30, By.ID, "auth_btn")
85+
assert elem is not None
86+
self._input_user_and_passwd_and_login()
87+
self._find_and_click(30, By.ID, "approve")
88+
self._assert_EOB_table_header_present()
89+
self._assert_EOB_table_records_present(10)
90+
91+
def test_node_sample_app_grant_access_no_demographic(self):
92+
self.driver.get("http://client:3000/")
93+
self.driver.set_window_size(1500, 1800)
94+
elem = self._find_and_click(30, By.ID, "auth_btn")
95+
assert elem is not None
96+
self._input_user_and_passwd_and_login()
97+
# select radio button "No Demographic Data"
98+
self._find_and_click(30, By.CSS_SELECTOR, "label:nth-child(5)")
99+
self._find_and_click(30, By.ID, "approve")
100+
self._assert_EOB_table_header_present()
101+
self._assert_EOB_table_records_present(10)
102+
103+
def test_node_sample_app_deny_access(self):
104+
self.driver.get("http://client:3000/")
105+
self.driver.set_window_size(1500, 1800)
106+
elem = self._find_and_click(30, By.ID, "auth_btn")
107+
assert elem is not None
108+
self._input_user_and_passwd_and_login()
109+
self._find_and_click(30, By.ID, "deny")
110+
self._assert_EOB_table_header_present()
111+
self._assert_EOB_table_records_present(0)
Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import axios from "axios";
2-
import { getLoggedInUser } from '../utils/user';
3-
import db from '../utils/db';
42
import app from '../Server';
53
import * as reqs from '../utils/request';
6-
import AuthorizationToken from "../entities/AuthorizationToken";
74

85
const BB2_BASE_URL = "https://sandbox.bluebutton.cms.gov";
96

@@ -23,20 +20,6 @@ const patient = { status: 200, data: { resource: "Patient" } };
2320

2421
const profile = { status: 200, data: { resource: "Profile" } };
2522

26-
const MOCK_AUTH_TOKEN_RESPONSE = {
27-
status: 200,
28-
data: {
29-
access_token: "access_token_foo_refreshed",
30-
expires_in: 36000,
31-
token_type: "Bearer",
32-
scope: ["scope1", "scope2", "scope3"],
33-
refresh_token: "refresh_token_bar_refreshed",
34-
patient: "-19990000000001",
35-
},
36-
};
37-
38-
const MOCK_AUTH_TOKEN = new AuthorizationToken(MOCK_AUTH_TOKEN_RESPONSE.data);
39-
4023
let server: any;
4124

4225
beforeAll(() => {
@@ -48,11 +31,12 @@ afterAll(() => {
4831
});
4932

5033
test("expect patient end point returns patient data.", async () => {
34+
jest.clearAllMocks();
5135
// mock patient returned at deeper layer
5236
jest.spyOn(reqs, 'get').mockImplementation((url) =>
5337
{
5438
if (url === BB2_PATIENT_URL) {
55-
return Promise.resolve(patient);
39+
return Promise.resolve(patient);
5640
} else {
5741
throw Error("Invalid end point URL: " + url);
5842
}
@@ -66,6 +50,7 @@ test("expect patient end point returns patient data.", async () => {
6650
});
6751

6852
test("expect profile end point returns profile data.", async () => {
53+
jest.clearAllMocks();
6954
// mock profile returned at lower level get
7055
jest.spyOn(reqs, 'get').mockImplementation((url) =>
7156
{
@@ -82,11 +67,12 @@ test("expect profile end point returns profile data.", async () => {
8267
});
8368

8469
test("expect coverage end point returns coverage data.", async () => {
70+
jest.clearAllMocks();
8571
// mock coverage returned at deeper layer
8672
jest.spyOn(reqs, 'get').mockImplementation((url) =>
8773
{
8874
if (url === BB2_COVERAGE_URL) {
89-
return Promise.resolve(coverage);
75+
return Promise.resolve(coverage);
9076
} else {
9177
throw Error("Invalid end point URL: " + url);
9278
}
@@ -99,23 +85,20 @@ test("expect coverage end point returns coverage data.", async () => {
9985
expect(response.data).toEqual(coverage.data);
10086
});
10187

102-
//test("expect eob end point returns eob data.", async () => {
103-
// const loggedInUser = getLoggedInUser(db);
104-
//
105-
// loggedInUser.authToken = MOCK_AUTH_TOKEN;
106-
//
107-
// // mock eob returned at lower level get
108-
// jest.spyOn(reqs, 'get').mockImplementation((url) =>
109-
// {
110-
// if (url === BB2_EOB_URL) {
111-
// return Promise.resolve(eob);
112-
// } else {
113-
// throw Error("Invalid end point URL: " + url);
114-
// }
115-
// }
116-
// );
117-
//
118-
// const response = await axios.get("http://localhost:3003/api/data/benefit-direct");
119-
// expect(response.status).toEqual(200);
120-
// expect(response.data).toEqual(eob.data);
121-
//});
88+
test("expect eob end point returns eob data.", async () => {
89+
jest.clearAllMocks();
90+
// mock eob returned at lower level get
91+
jest.spyOn(reqs, 'get').mockImplementation((url) =>
92+
{
93+
if (url === BB2_EOB_URL) {
94+
return Promise.resolve(eob);
95+
} else {
96+
throw Error("Invalid end point URL: " + url);
97+
}
98+
}
99+
);
100+
101+
const response = await axios.get("http://localhost:3003/api/data/benefit-direct");
102+
expect(response.status).toEqual(200);
103+
expect(response.data).toEqual(eob.data);
104+
});

0 commit comments

Comments
 (0)