Skip to content

Commit e723f32

Browse files
feat: checking cluster and app state during ci (#275)
access kots through web and make sure the app is ready and the cluster is up to date.
1 parent b1d91dd commit e723f32

File tree

4 files changed

+311
-0
lines changed

4 files changed

+311
-0
lines changed

e2e/install_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package e2e
22

33
import (
4+
"encoding/json"
45
"testing"
56

67
"github.com/replicatedhq/embedded-cluster/e2e/cluster"
@@ -30,6 +31,36 @@ func TestSingleNodeInstallation(t *testing.T) {
3031
if _, _, err := RunCommandOnNode(t, tc, 0, line); err != nil {
3132
t.Fatalf("fail to install embedded-cluster on node %s: %v", tc.Nodes[0], err)
3233
}
34+
t.Log("installing puppeteer on node 0")
35+
line = []string{"install-puppeteer.sh"}
36+
if stdout, stderr, err := RunCommandOnNode(t, tc, 0, line); err != nil {
37+
t.Log("stdout:", stdout)
38+
t.Log("stderr:", stderr)
39+
t.Fatalf("fail to install puppeteer on node %s: %v", tc.Nodes[0], err)
40+
}
41+
t.Log("accessing kotsadm interface and checking app and cluster state")
42+
line = []string{"puppeteer.sh", "check-app-and-cluster-status.js", "10.0.0.2"}
43+
stdout, stderr, err := RunCommandOnNode(t, tc, 0, line)
44+
if err != nil {
45+
t.Log("stdout:", stdout)
46+
t.Log("stderr:", stderr)
47+
t.Fatalf("fail to access kotsadm interface and state: %v", err)
48+
}
49+
type response struct {
50+
App string `json:"app"`
51+
Cluster string `json:"cluster"`
52+
}
53+
var r response
54+
if err := json.Unmarshal([]byte(stdout), &r); err != nil {
55+
t.Log("stdout:", stdout)
56+
t.Log("stderr:", stderr)
57+
t.Fatalf("fail to parse script response: %v", err)
58+
}
59+
if r.App != "Ready" || r.Cluster != "Up to date" {
60+
t.Log("stdout:", stdout)
61+
t.Log("stderr:", stderr)
62+
t.Fatalf("cluster or app not ready: %s", stdout)
63+
}
3364
}
3465

3566
func TestSingleNodeInstallationRockyLinux8(t *testing.T) {
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#!/usr/bin/env node
2+
3+
/*
4+
* this script has been generated with chrome recorder and then pasted here.
5+
* some parts were manually changed, these are flagged with a CUSTOM comment.
6+
* all logging has also been manually added (process.stderr.write() calls).
7+
* this script is meant to be run as an argument to the `puppeteer.sh` script.
8+
*/
9+
10+
const puppeteer = require('puppeteer'); // v20.7.4 or later
11+
12+
(async () => {
13+
const browser = await puppeteer.launch(
14+
{
15+
headless: 'new',
16+
// CUSTOM: added the following line to fix the "No usable sandbox!" error.
17+
args: ['--no-sandbox', '--disable-setuid-sandbox']
18+
}
19+
);
20+
const page = await browser.newPage();
21+
const timeout = 5000;
22+
page.setDefaultTimeout(timeout);
23+
const args = process.argv.slice(2);
24+
if (args.length !== 1) {
25+
throw new Error('usage: check-app-and-cluster-status.js <kotsadm-ip>');
26+
}
27+
28+
{
29+
const targetPage = page;
30+
await targetPage.setViewport({
31+
width: 1920,
32+
height: 934
33+
})
34+
}
35+
{
36+
process.stderr.write("opening a new tab\n");
37+
const targetPage = page;
38+
const promises = [];
39+
const startWaitingForEvents = () => {
40+
promises.push(targetPage.waitForNavigation());
41+
}
42+
startWaitingForEvents();
43+
await targetPage.goto('chrome://new-tab-page/');
44+
await Promise.all(promises);
45+
}
46+
{
47+
process.stderr.write("acessing kotsadm on port 30000\n");
48+
const targetPage = page;
49+
const promises = [];
50+
const startWaitingForEvents = () => {
51+
promises.push(targetPage.waitForNavigation());
52+
}
53+
startWaitingForEvents();
54+
// CUSTOM: using the command line argument.
55+
await targetPage.goto(`http://${args[0]}:30000/`);
56+
await Promise.all(promises);
57+
}
58+
{
59+
process.stderr.write("waiting and clickin on the 'Continue to Setup' button\n");
60+
const targetPage = page;
61+
const promises = [];
62+
const startWaitingForEvents = () => {
63+
promises.push(targetPage.waitForNavigation());
64+
}
65+
await puppeteer.Locator.race([
66+
targetPage.locator('::-p-aria(Continue to Setup)'),
67+
targetPage.locator('button'),
68+
targetPage.locator('::-p-xpath(/html/body/div/div/div[2]/div[1]/div[4]/button)'),
69+
targetPage.locator(':scope >>> button'),
70+
targetPage.locator('::-p-text(Continue to Setup)')
71+
])
72+
.setTimeout(timeout)
73+
.on('action', () => startWaitingForEvents())
74+
.click({
75+
offset: {
76+
x: 44,
77+
y: 15,
78+
},
79+
});
80+
await Promise.all(promises);
81+
}
82+
{
83+
process.stderr.write("waiting and clicking on 'Advanced' to move on with the certificate\n");
84+
const targetPage = page;
85+
await puppeteer.Locator.race([
86+
targetPage.locator('::-p-aria(Advanced)'),
87+
targetPage.locator('#details-button'),
88+
targetPage.locator('::-p-xpath(//*[@id=\\"details-button\\"])'),
89+
targetPage.locator(':scope >>> #details-button'),
90+
targetPage.locator('::-p-text(Advanced)')
91+
])
92+
.setTimeout(timeout)
93+
.click({
94+
offset: {
95+
x: 77,
96+
y: 21.2421875,
97+
},
98+
});
99+
}
100+
{
101+
process.stderr.write("waiting and clicking on 'Proceed' to move on with the certificate\n");
102+
const targetPage = page;
103+
// CUSTOM: using command line argument.
104+
await puppeteer.Locator.race([
105+
targetPage.locator(`::-p-aria(Proceed to ${args[0]} \\(unsafe\\))`),
106+
targetPage.locator('#proceed-link'),
107+
targetPage.locator('::-p-xpath(//*[@id=\\"proceed-link\\"])'),
108+
targetPage.locator(':scope >>> #proceed-link'),
109+
targetPage.locator(`::-p-text(Proceed to ${args[0]})`)
110+
])
111+
.setTimeout(timeout)
112+
.click({
113+
offset: {
114+
x: 48,
115+
y: 7.7421875,
116+
},
117+
});
118+
}
119+
{
120+
process.stderr.write("going to the /tls endpoint\n");
121+
const targetPage = page;
122+
const promises = [];
123+
const startWaitingForEvents = () => {
124+
promises.push(targetPage.waitForNavigation());
125+
}
126+
startWaitingForEvents();
127+
// CUSTOM: using the command line argument.
128+
await targetPage.goto(`https://${args[0]}:30000/tls`);
129+
await Promise.all(promises);
130+
}
131+
{
132+
process.stderr.write("waiting and clicking on 'Continue'\n");
133+
const targetPage = page;
134+
const promises = [];
135+
const startWaitingForEvents = () => {
136+
promises.push(targetPage.waitForNavigation());
137+
}
138+
await puppeteer.Locator.race([
139+
targetPage.locator('::-p-aria(Continue)'),
140+
targetPage.locator('button'),
141+
targetPage.locator('::-p-xpath(//*[@id=\\"upload-form\\"]/div[6]/button)'),
142+
targetPage.locator(':scope >>> button'),
143+
targetPage.locator('::-p-text(Continue\n )')
144+
])
145+
.setTimeout(timeout)
146+
.on('action', () => startWaitingForEvents())
147+
.click({
148+
offset: {
149+
x: 45,
150+
y: 6,
151+
},
152+
});
153+
await Promise.all(promises);
154+
}
155+
{
156+
process.stderr.write("waiting and clicking in the password field\n");
157+
const targetPage = page;
158+
await puppeteer.Locator.race([
159+
targetPage.locator('::-p-aria(password)'),
160+
targetPage.locator('input'),
161+
targetPage.locator('::-p-xpath(//*[@id=\\"app\\"]/div/div[2]/div/div/div/div[2]/div/div/div[1]/input)'),
162+
targetPage.locator(':scope >>> input')
163+
])
164+
.setTimeout(timeout)
165+
.click({
166+
offset: {
167+
x: 35,
168+
y: 17.5078125,
169+
},
170+
});
171+
}
172+
{
173+
process.stderr.write("typing the password\n");
174+
const targetPage = page;
175+
await puppeteer.Locator.race([
176+
targetPage.locator('::-p-aria(password)'),
177+
targetPage.locator('input'),
178+
targetPage.locator('::-p-xpath(//*[@id=\\"app\\"]/div/div[2]/div/div/div/div[2]/div/div/div[1]/input)'),
179+
targetPage.locator(':scope >>> input')
180+
])
181+
.setTimeout(timeout)
182+
.fill('password');
183+
}
184+
{
185+
process.stderr.write("clicking in the Log in button\n");
186+
const targetPage = page;
187+
await puppeteer.Locator.race([
188+
targetPage.locator('::-p-aria(Log in)'),
189+
targetPage.locator('button'),
190+
targetPage.locator('::-p-xpath(//*[@id=\\"app\\"]/div/div[2]/div/div/div/div[2]/div/div/div[2]/button)'),
191+
targetPage.locator(':scope >>> button')
192+
])
193+
.setTimeout(timeout)
194+
.click({
195+
offset: {
196+
x: 27,
197+
y: 22.5078125,
198+
},
199+
});
200+
}
201+
{
202+
// CUSTOM: finding the element with the app state and extracting its content.
203+
let state = {app: "", cluster:""};
204+
process.stderr.write("waiting and fetching the application and cluster state\n");
205+
const targetPage = page;
206+
await targetPage.waitForSelector('#app > div > div.flex1.flex-column.u-overflow--auto.tw-relative > div > div > div > div.flex-column.flex1.u-position--relative.u-overflow--auto.u-padding--20 > div > div > div.flex.flex1.alignItems--center > div.u-marginLeft--20 > div > div:nth-child(1) > span:nth-child(5)');
207+
let elementContent = await targetPage.evaluate(() => {
208+
const element = document.querySelector('#app > div > div.flex1.flex-column.u-overflow--auto.tw-relative > div > div > div > div.flex-column.flex1.u-position--relative.u-overflow--auto.u-padding--20 > div > div > div.flex.flex1.alignItems--center > div.u-marginLeft--20 > div > div:nth-child(1) > span:nth-child(5)');
209+
return element ? element.textContent : null;
210+
});
211+
if (elementContent) {
212+
state.cluster = elementContent;
213+
}
214+
await targetPage.waitForSelector('#app > div > div.flex1.flex-column.u-overflow--auto.tw-relative > div > div > div > div.flex-column.flex1.u-position--relative.u-overflow--auto.u-padding--20 > div > div > div.flex.flex1.alignItems--center > div.u-marginLeft--20 > div > div:nth-child(1) > span:nth-child(2)');
215+
elementContent = await targetPage.evaluate(() => {
216+
const element = document.querySelector('#app > div > div.flex1.flex-column.u-overflow--auto.tw-relative > div > div > div > div.flex-column.flex1.u-position--relative.u-overflow--auto.u-padding--20 > div > div > div.flex.flex1.alignItems--center > div.u-marginLeft--20 > div > div:nth-child(1) > span:nth-child(2)');
217+
return element ? element.textContent : null;
218+
});
219+
if (elementContent) {
220+
state.app = elementContent;
221+
}
222+
console.log(JSON.stringify(state));
223+
}
224+
225+
await browser.close();
226+
227+
})().catch(err => {
228+
console.error(err);
229+
process.exit(1);
230+
});

e2e/scripts/install-puppeteer.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
set -euox pipefail
3+
4+
apt-get update -y
5+
apt-get install -y \
6+
ca-certificates \
7+
curl \
8+
gnupg \
9+
chromium-browser \
10+
libx11-xcb1 \
11+
libxcomposite1 \
12+
libasound2 \
13+
libatk1.0-0 \
14+
libatk-bridge2.0-0 \
15+
libcairo2 \
16+
libcups2 \
17+
libdbus-1-3 \
18+
libexpat1 \
19+
libfontconfig1 \
20+
libgbm1 \
21+
libgcc1 \
22+
libglib2.0-0 \
23+
libgtk-3-0 \
24+
libnspr4 \
25+
libpango-1.0-0 \
26+
libpangocairo-1.0-0 \
27+
libstdc++6 \
28+
libx11-6 \
29+
libx11-xcb1 \
30+
libxcb1 \
31+
libxcomposite1 \
32+
libxcursor1 \
33+
libxdamage1 \
34+
libxext6 \
35+
libxfixes3 \
36+
libxi6 \
37+
libxrandr2 \
38+
libxrender1 \
39+
libxss1 \
40+
libnss3 \
41+
libxtst6
42+
43+
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
44+
NODE_MAJOR=20
45+
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
46+
apt-get update && apt-get install nodejs -y
47+
npm install -g puppeteer

e2e/scripts/puppeteer.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
NODE_PATH="$(npm root -g)" "$@"

0 commit comments

Comments
 (0)