Skip to content

Commit 0fec6fa

Browse files
committed
Add setting up Instance via UI instead of GraphQL
1 parent 2f54cec commit 0fec6fa

6 files changed

+399
-253
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//go:build cloud_slack_dev_e2e
2+
3+
package cloud_slack_dev_e2e
4+
5+
import (
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"os/exec"
10+
"strings"
11+
"testing"
12+
"time"
13+
14+
"github.com/go-rod/rod"
15+
"github.com/go-rod/rod/lib/input"
16+
"github.com/go-rod/rod/lib/proto"
17+
"github.com/mattn/go-shellwords"
18+
"github.com/stretchr/testify/require"
19+
20+
gqlModel "github.com/kubeshop/botkube-cloud/botkube-cloud-backend/pkg/graphql"
21+
)
22+
23+
const (
24+
authHeaderName = "Authorization"
25+
awaitInstanceStatusChange = 2 * time.Minute
26+
orgQueryParam = "organizationId"
27+
)
28+
29+
type BotkubeCloudPage struct {
30+
cfg E2ESlackConfig
31+
page *Page
32+
33+
AuthHeaderValue string
34+
GQLEndpoint string
35+
ConnectedDeploy *gqlModel.Deployment
36+
}
37+
38+
func NewBotkubeCloudPage(t *testing.T, cfg E2ESlackConfig) *BotkubeCloudPage {
39+
return &BotkubeCloudPage{
40+
page: &Page{t: t, cfg: cfg},
41+
cfg: cfg,
42+
GQLEndpoint: fmt.Sprintf("%s/%s", cfg.BotkubeCloud.APIBaseURL, cfg.BotkubeCloud.APIGraphQLEndpoint),
43+
}
44+
}
45+
46+
func (p *BotkubeCloudPage) NavigateAndLogin(t *testing.T, page *rod.Page) {
47+
t.Log("Log into Botkube Cloud Dashboard")
48+
49+
p.page.Page = page
50+
51+
p.page.MustNavigate(appendOrgIDQueryParam(t, p.cfg.BotkubeCloud.UIBaseURL, p.cfg.BotkubeCloud.TeamOrganizationID))
52+
p.page.MustWaitNavigation()
53+
54+
p.page.MustElement(`input[name="username"]`).MustInput(p.cfg.BotkubeCloud.Email)
55+
p.page.MustElement(`input[name="password"]`).MustInput(p.cfg.BotkubeCloud.Password)
56+
p.page.MustElementR("button", "^Continue$").MustClick()
57+
p.page.Screenshot()
58+
}
59+
60+
func (p *BotkubeCloudPage) HideCookieBanner(t *testing.T) {
61+
t.Log("Hide Botkube cookie banner")
62+
p.page.MustElementR("button", "^Decline$").MustClick()
63+
p.page.Screenshot()
64+
}
65+
66+
func (p *BotkubeCloudPage) CaptureBearerToken(t *testing.T, browser *rod.Browser) func() {
67+
t.Logf("Starting hijacking requests to %q to get the bearer token...", p.GQLEndpoint)
68+
69+
router := browser.HijackRequests()
70+
router.MustAdd(p.GQLEndpoint, func(ctx *rod.Hijack) {
71+
if p.AuthHeaderValue != "" {
72+
ctx.ContinueRequest(&proto.FetchContinueRequest{})
73+
return
74+
}
75+
76+
if ctx.Request != nil && ctx.Request.Method() != http.MethodPost {
77+
ctx.ContinueRequest(&proto.FetchContinueRequest{})
78+
return
79+
}
80+
81+
require.NotNil(t, ctx.Request)
82+
p.AuthHeaderValue = ctx.Request.Header(authHeaderName)
83+
ctx.ContinueRequest(&proto.FetchContinueRequest{})
84+
})
85+
go router.Run()
86+
return router.MustStop
87+
}
88+
89+
func (p *BotkubeCloudPage) CreateNewInstance(t *testing.T, name string) {
90+
t.Log("Create new Botkube Instance")
91+
92+
p.page.MustElement("h6#create-instance").MustClick()
93+
p.page.MustElement(`input[name="name"]`).MustSelectAllText().MustInput(name)
94+
p.page.Screenshot()
95+
96+
// persist connected deploy info
97+
_, id, _ := strings.Cut(p.page.MustInfo().URL, "add/")
98+
p.ConnectedDeploy = &gqlModel.Deployment{
99+
Name: name,
100+
ID: id,
101+
}
102+
}
103+
104+
func (p *BotkubeCloudPage) InstallAgentInCluster(t *testing.T, botkubeBinary string) {
105+
t.Log("Installing Botkube using Botkube CLI")
106+
107+
installCmd := p.page.MustElement("div#install-upgrade-cmd > kbd").MustText()
108+
109+
args, err := shellwords.Parse(installCmd)
110+
args = append(args, "--auto-approve")
111+
require.NoError(t, err)
112+
113+
cmd := exec.Command(botkubeBinary, args[1:]...)
114+
installOutput, err := cmd.CombinedOutput()
115+
t.Log(string(installOutput))
116+
require.NoError(t, err)
117+
118+
p.page.MustElement("button#cluster-connected").MustClick()
119+
}
120+
121+
func (p *BotkubeCloudPage) OpenSlackAppIntegrationPage(t *testing.T) {
122+
p.page.MustElement(`button[aria-label="Add tab"]`).MustClick()
123+
p.page.MustWaitStable()
124+
p.page.MustElementR("button", "^Slack$").MustClick()
125+
p.page.MustWaitStable()
126+
p.page.Screenshot()
127+
128+
p.page.MustElementR("a", "Add to Slack").MustClick()
129+
}
130+
131+
// ReAddSlackPlatformIfShould add the slack platform again as the page was often not refreshed with a newly connected Slack Workspace.
132+
// It only occurs with headless mode.
133+
// TODO(@pkosiec): Do you have a better idea how to fix it?
134+
func (p *BotkubeCloudPage) ReAddSlackPlatformIfShould(t *testing.T, isHeadless bool) {
135+
if !isHeadless {
136+
return
137+
}
138+
139+
t.Log("Re-adding Slack platform")
140+
141+
p.page.MustActivate()
142+
p.page.MustElement(`button[aria-label="remove"]`).MustClick()
143+
p.page.MustElement(`button[aria-label="Add tab"]`).MustClick()
144+
p.page.MustElementR("button", "^Slack$").MustClick()
145+
p.page.Screenshot()
146+
}
147+
148+
func (p *BotkubeCloudPage) VerifyDeploymentStatus(t *testing.T, status string) {
149+
t.Logf("Waiting for status '%s'", status)
150+
p.page.Timeout(awaitInstanceStatusChange).MustElementR("div#deployment-status", status)
151+
}
152+
153+
func (p *BotkubeCloudPage) SetupSlackWorkspace(t *testing.T, channel string) {
154+
t.Logf("Selecting newly connected %q Slack Workspace", p.cfg.Slack.WorkspaceName)
155+
156+
p.page.MustElement(`input[type="search"]`).
157+
MustInput(p.cfg.Slack.WorkspaceName).
158+
MustType(input.Enter)
159+
p.page.Screenshot()
160+
161+
// filter by channel, to make sure that it's visible on the first table page, in order to select it in the next step
162+
t.Log("Filtering by channel name")
163+
p.page.Keyboard.MustType(input.End) // scroll bottom, as the footer collides with selecting filter
164+
p.page.MustElement("table th:nth-child(3) span.ant-dropdown-trigger.ant-table-filter-trigger").MustFocus().MustClick()
165+
166+
t.Log("Selecting channel checkbox")
167+
p.page.MustElement("input#name-channel").MustInput(channel).MustType(input.Enter)
168+
p.page.MustElement(fmt.Sprintf(`input[type="checkbox"][name="%s"]`, channel)).MustClick()
169+
p.page.Screenshot()
170+
}
171+
172+
func (p *BotkubeCloudPage) FinishWizard(t *testing.T) {
173+
t.Log("Navigating to plugin selection")
174+
p.page.MustElementR("button", "/^Next$/i").MustClick().MustWaitStable()
175+
p.page.Screenshot()
176+
177+
t.Log("Using pre-selected plugins. Navigating to wizard summary")
178+
p.page.MustElementR("button", "/^Next$/i").MustClick().MustWaitStable()
179+
p.page.Screenshot()
180+
181+
t.Log("Submitting changes")
182+
p.page.MustElementR("button", "/^Deploy changes$/i").MustClick().MustWaitStable()
183+
p.page.Screenshot()
184+
}
185+
186+
func (p *BotkubeCloudPage) UpdateKubectlNamespace(t *testing.T) {
187+
t.Log("Updating 'kubectl' namespace property")
188+
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustClick()
189+
p.page.MustElement(`button[id^="botkube/kubectl_"]`).MustClick()
190+
p.page.MustElement(`div[data-node-key="ui-form"]`).MustClick()
191+
p.page.MustElementR("input#root_defaultNamespace", "default").MustSelectAllText().MustInput("kube-system")
192+
p.page.MustElementR("button", "/^Update$/i").MustClick()
193+
194+
t.Log("Submitting changes")
195+
p.page.MustElementR("button", "/^Deploy changes$/i").MustClick().MustWaitStable() // use the case-insensitive flag "i"
196+
}
197+
198+
func (p *BotkubeCloudPage) VerifyUpdatedKubectlNamespace(t *testing.T) {
199+
t.Log("Verifying that the 'namespace' value was updated and persisted properly")
200+
201+
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustClick()
202+
p.page.MustElement(`button[id^="botkube/kubectl_"]`).MustClick()
203+
p.page.MustElement(`div[data-node-key="ui-form"]`).MustClick()
204+
p.page.MustElementR("input#root_defaultNamespace", "kube-system")
205+
}
206+
207+
func appendOrgIDQueryParam(t *testing.T, inURL, orgID string) string {
208+
parsedURL, err := url.Parse(inURL)
209+
require.NoError(t, err)
210+
queryValues := parsedURL.Query()
211+
queryValues.Set(orgQueryParam, orgID)
212+
parsedURL.RawQuery = queryValues.Encode()
213+
214+
return parsedURL.String()
215+
}

0 commit comments

Comments
 (0)