Skip to content

Commit c905661

Browse files
committed
Add temporary workflow to test e2e tests
1 parent ee243b3 commit c905661

File tree

4 files changed

+120
-43
lines changed

4 files changed

+120
-43
lines changed
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Test UI E2E tests on PR
2+
3+
on:
4+
push:
5+
branches:
6+
- extend-ui-tests
7+
8+
env:
9+
GIT_USER: botkube-dev
10+
HELM_VERSION: v3.9.0
11+
K3D_VERSION: v5.4.6
12+
IMAGE_REGISTRY: "ghcr.io"
13+
IMAGE_REPOSITORY: "kubeshop/botkube"
14+
IMAGE_TAG: v9.99.9-dev # TODO: Use commit hash tag to make the predictable builds for each commit on branch
15+
16+
jobs:
17+
cloud-slack-dev-e2e:
18+
name: Botkube Cloud Slack Dev E2E
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
packages: read
23+
concurrency:
24+
group: cloud-slack-dev-e2e
25+
cancel-in-progress: false
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Run e2e tests
31+
uses: ./.github/actions/cloud-slack-e2e
32+
with:
33+
access_token: ${{ secrets.E2E_TEST_GH_DEV_ACCOUNT_PAT }}
34+
35+
slack_workspace_name: ${{ secrets.E2E_DEV_SLACK_WORKSPACE_NAME }}
36+
slack_email: ${{ secrets.E2E_DEV_SLACK_EMAIL }}
37+
slack_password: ${{ secrets.E2E_DEV_SLACK_USER_PASSWORD }}
38+
slack_bot_display_name: "BotkubeDev"
39+
slack_tester_bot_token: ${{ secrets.E2E_DEV_SLACK_TESTER_BOT_TOKEN }}
40+
slack_tester_bot_name: "botkubedev"
41+
42+
botkube_cloud_api_base_url: "https://api-dev.botkube.io"
43+
botkube_cloud_email: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_EMAIL }}
44+
botkube_cloud_password: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_PASSWORD }}
45+
botkube_cloud_team_organization_id: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_TEAM_ORGANIZATION_ID }}
46+
47+
slack_alerts_webhook: ${{ secrets.SLACK_CI_ALERTS_WEBHOOK }}
48+
49+
e2e_type: "DEV"

test/cloud-slack-dev-e2e/botkube_page_helpers_test.go

+31-13
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (p *BotkubeCloudPage) SetupSlackWorkspace(t *testing.T, channel string) {
160160

161161
// filter by channel, to make sure that it's visible on the first table page, in order to select it in the next step
162162
t.Log("Filtering by channel name")
163-
p.page.Mouse.MustScroll(10, 5000) // scroll bottom, as the footer collides with selecting filter
163+
p.page.Mouse.MustScroll(10, 5000) // scroll bottom, as the footer collides with selecting filter
164164
p.page.Screenshot()
165165
p.page.MustElement("table th:nth-child(3) span.ant-dropdown-trigger.ant-table-filter-trigger").MustClick()
166166

@@ -172,40 +172,58 @@ func (p *BotkubeCloudPage) SetupSlackWorkspace(t *testing.T, channel string) {
172172

173173
func (p *BotkubeCloudPage) FinishWizard(t *testing.T) {
174174
t.Log("Navigating to plugin selection")
175-
p.page.Screenshot()
176-
177-
p.page.MustElementR("button", "/^Next$/i").MustClick()
178-
p.page.Screenshot()
175+
p.page.Screenshot("before-first-next")
176+
177+
p.page.MustElementR("button", "/^Next$/i").
178+
MustWaitEnabled().
179+
// we need to wait, otherwise, we click the same 'Next' button twice, before the query is executed, and we are really
180+
// moved to the next step. If we have the navigation updated for each step, it will be resolved.
181+
MustClick().MustWaitStable()
182+
183+
p.page.Screenshot("after-first-next")
179184

180185
t.Log("Using pre-selected plugins. Navigating to wizard summary")
181-
p.page.MustElementR("button", "/^Next$/i").MustClick()
182-
p.page.Screenshot()
186+
p.page.MustElementR("button", "/^Next$/i").
187+
MustWaitEnabled().
188+
// we need to wait, otherwise, we click the same 'Next' button twice, before the query is executed, and we are really
189+
// moved to the next step. If we have the navigation updated for each step, it will be resolved.
190+
MustClick().MustWaitStable()
191+
p.page.Screenshot("after-second-next")
183192

184193
t.Log("Submitting changes")
185-
p.page.MustElementR("button", "/^Deploy changes$/i").MustClick()
194+
p.page.MustElementR("button", "/^Deploy changes$/i").
195+
MustWaitEnabled().
196+
MustClick()
197+
p.page.Screenshot("after-deploy-changes")
198+
199+
// wait till gql mutation passes, and navigates to install details, otherwise, we could navigate to instance details with state 'draft'
200+
p.page.MustWaitNavigation()
186201
p.page.Screenshot()
187202
}
188203

189204
func (p *BotkubeCloudPage) UpdateKubectlNamespace(t *testing.T) {
190205
t.Log("Updating 'kubectl' namespace property")
191-
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustClick()
206+
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustFocus().MustClick().MustWaitStable()
207+
208+
p.page.MustWaitStable()
192209
p.page.MustElement(`button[id^="botkube/kubectl_"]`).MustClick()
193210
p.page.MustElement(`div[data-node-key="ui-form"]`).MustClick()
194211
p.page.MustElementR("input#root_defaultNamespace", "default").MustSelectAllText().MustInput("kube-system")
195212
p.page.MustElementR("button", "/^Update$/i").MustClick()
196213

197214
t.Log("Submitting changes")
198-
p.page.MustElementR("button", "/^Deploy changes$/i").MustClick().MustWaitStable() // use the case-insensitive flag "i"
215+
p.page.MustElementR("button", "/^Deploy changes$/i").MustClick().MustWaitStable()
199216
}
200217

201218
func (p *BotkubeCloudPage) VerifyUpdatedKubectlNamespace(t *testing.T) {
202219
t.Log("Verifying that the 'namespace' value was updated and persisted properly")
203220

204-
p.page.Screenshot()
205-
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustClick()
221+
p.page.Screenshot("before-selecting-plugins-tab")
222+
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustFocus().MustClick().MustWaitStable()
223+
p.page.Screenshot("after-selecting-plugins-tab")
206224
p.page.MustElement(`button[id^="botkube/kubectl_"]`).MustClick()
207225
p.page.MustElement(`div[data-node-key="ui-form"]`).MustClick()
208-
p.page.Screenshot()
226+
p.page.Screenshot("after-selecting-kubectl-cfg-form")
209227
p.page.MustElementR("input#root_defaultNamespace", "kube-system")
210228
}
211229

test/cloud-slack-dev-e2e/cloud_slack_dev_e2e_test.go

+30-25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/go-rod/rod/lib/launcher"
2222
"github.com/go-rod/rod/lib/launcher/flags"
2323
"github.com/hasura/go-graphql-client"
24+
"github.com/sanity-io/litter"
2425
"github.com/stretchr/testify/assert"
2526
"github.com/stretchr/testify/require"
2627
"github.com/vrischmann/envconfig"
@@ -436,40 +437,40 @@ func removeSourcesAndAddActions(t *testing.T, gql *graphql.Client, existingDeplo
436437
var updateInput struct {
437438
UpdateDeployment gqlModel.Deployment `graphql:"updateDeployment(id: $id, input: $input)"`
438439
}
440+
439441
var updatePluginGroup []*gqlModel.PluginConfigurationGroupUpdateInput
440442
for _, createdPlugin := range existingDeployment.Plugins {
441-
var pluginConfigs []*gqlModel.PluginConfigurationUpdateInput
442-
pluginConfig := gqlModel.PluginConfigurationUpdateInput{
443-
Name: createdPlugin.ConfigurationName,
444-
Configuration: createdPlugin.Configuration,
445-
}
446-
pluginConfigs = append(pluginConfigs, &pluginConfig)
447-
plugin := gqlModel.PluginConfigurationGroupUpdateInput{
448-
ID: &createdPlugin.ID,
449-
Name: createdPlugin.Name,
450-
Type: createdPlugin.Type,
451-
DisplayName: createdPlugin.DisplayName,
452-
Configurations: pluginConfigs,
453-
}
454-
updatePluginGroup = append(updatePluginGroup, &plugin)
455-
}
456-
var updatePlugins []*gqlModel.PluginsUpdateInput
457-
updatePlugin := gqlModel.PluginsUpdateInput{
458-
Groups: updatePluginGroup,
443+
updatePluginGroup = append(updatePluginGroup, &gqlModel.PluginConfigurationGroupUpdateInput{
444+
ID: &createdPlugin.ID,
445+
Name: createdPlugin.Name,
446+
Type: createdPlugin.Type,
447+
DisplayName: createdPlugin.DisplayName,
448+
Configurations: []*gqlModel.PluginConfigurationUpdateInput{
449+
{
450+
Name: createdPlugin.ConfigurationName,
451+
Configuration: createdPlugin.Configuration,
452+
},
453+
},
454+
})
459455
}
460-
updatePlugins = append(updatePlugins, &updatePlugin)
461456

462457
platforms := gqlModel.PlatformsUpdateInput{}
463-
464458
for _, slack := range existingDeployment.Platforms.CloudSlacks {
465459
var channelUpdateInputs []*gqlModel.ChannelBindingsByNameAndIDUpdateInput
466460
for _, channel := range slack.Channels {
461+
var executors []*string
462+
for _, e := range channel.Bindings.Executors {
463+
if strings.Contains(strings.ToLower(e), "ai") { // AI is also a source plugin
464+
continue
465+
}
466+
executors = append(executors, &e)
467+
}
467468
channelUpdateInputs = append(channelUpdateInputs, &gqlModel.ChannelBindingsByNameAndIDUpdateInput{
468469
ChannelID: "", // this is used for UI only so we don't need to provide it
469470
Name: channel.Name,
470471
Bindings: &gqlModel.BotBindingsUpdateInput{
471-
Sources: nil,
472-
Executors: []*string{&channel.Bindings.Executors[0]},
472+
Sources: []*string{},
473+
Executors: executors,
473474
},
474475
})
475476
}
@@ -486,14 +487,18 @@ func removeSourcesAndAddActions(t *testing.T, gql *graphql.Client, existingDeplo
486487
"input": gqlModel.DeploymentUpdateInput{
487488
Name: existingDeployment.Name,
488489
ResourceVersion: existingDeployment.ResourceVersion,
489-
Plugins: updatePlugins,
490-
Platforms: &platforms,
491-
Actions: CreateActionUpdateInput(existingDeployment),
490+
Plugins: []*gqlModel.PluginsUpdateInput{
491+
{Groups: updatePluginGroup},
492+
},
493+
Platforms: &platforms,
494+
Actions: CreateActionUpdateInput(existingDeployment),
492495
},
493496
}
497+
litter.Dump(updateVariables)
494498
err := gql.Mutate(context.Background(), &updateInput, updateVariables)
495499
require.NoError(t, err)
496500

501+
litter.Dump(updateInput.UpdateDeployment)
497502
return &updateInput.UpdateDeployment
498503
}
499504

test/cloud-slack-dev-e2e/page_helpers_test.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const (
2323
// Currently, we get:
2424
// This browser won’t be supported starting September 1st, 2024. Update your browser to keep using Slack. Learn more:
2525
// https://slack.com/intl/en-gb/help/articles/1500001836081-Slack-support-life-cycle-for-operating-systems-app-versions-and-browsers
26-
chromeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
26+
chromeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
2727
)
2828

2929
type Page struct {
@@ -32,7 +32,7 @@ type Page struct {
3232
cfg E2ESlackConfig
3333
}
3434

35-
func (p Page) Screenshot() {
35+
func (p Page) Screenshot(suffix ...string) {
3636
p.t.Helper()
3737
if p.cfg.ScreenshotsDir == "" {
3838
return
@@ -41,7 +41,7 @@ func (p Page) Screenshot() {
4141
pathParts := strings.Split(p.cfg.ScreenshotsDir, "/")
4242
pathParts = append(pathParts)
4343

44-
filePath := filepath.Join(p.cfg.ScreenshotsDir, fmt.Sprintf("%d.png", time.Now().UnixNano()))
44+
filePath := filepath.Join(p.cfg.ScreenshotsDir, fmt.Sprintf("%d%s.png", time.Now().UnixNano(), screenshotSuffix(suffix)))
4545

4646
logMsg := fmt.Sprintf("Saving screenshot to %q", filePath)
4747
if p.cfg.DebugMode {
@@ -63,6 +63,13 @@ func (p Page) Screenshot() {
6363
assert.NoError(p.t, err)
6464
}
6565

66+
func screenshotSuffix(suffix []string) string {
67+
if len(suffix) == 0 {
68+
return ""
69+
}
70+
return "-" + strings.Join(suffix, "-")
71+
}
72+
6673
func closePage(t *testing.T, name string, page *rod.Page) {
6774
t.Helper()
6875
err := page.Close()
@@ -75,7 +82,6 @@ func closePage(t *testing.T, name string, page *rod.Page) {
7582
}
7683
}
7784

78-
7985
func newBrowserPage(t *testing.T, browser *rod.Browser, cfg E2ESlackConfig) *rod.Page {
8086
t.Helper()
8187

@@ -88,4 +94,3 @@ func newBrowserPage(t *testing.T, browser *rod.Browser, cfg E2ESlackConfig) *rod
8894
page.MustSetViewport(1200, 1080, 1, false)
8995
return page
9096
}
91-

0 commit comments

Comments
 (0)