Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting up an Instance via the UI instead of GraphQL #1458

Merged
merged 6 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 12 additions & 14 deletions .github/actions/cloud-slack-e2e/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ inputs:
botkube_cloud_team_organization_id:
description: 'BotKube Cloud Team Organization ID'
required: true
botkube_cloud_free_organization_id:
description: 'BotKube Cloud Free Organization ID'
required: true
botkube_cloud_plugin_repo_url:
description: 'BotKube Cloud Plugin Repo URL'
required: true

slack_alerts_webhook:
description: 'Slack Alerts Webhook'
Expand All @@ -55,11 +49,6 @@ inputs:
runs:
using: "composite"
steps:
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: ${{ env.HELM_VERSION }}

- name: Download k3d
shell: bash
run: "wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | TAG=${K3D_VERSION} bash"
Expand All @@ -69,6 +58,17 @@ runs:
shell: bash
run: "k3d cluster create cloud-slack-e2e-cluster --wait --timeout=5m"

- name: Download Botkube CLI
shell: bash
run: |
curl -Lo botkube https://github.com/kubeshop/botkube/releases/download/v1.12.0/botkube-linux-amd64
chmod +x botkube

- name: Add Botkube CLI to env
shell: bash
run: |
echo BOTKUBE_CLI_BINARY_PATH="$PWD/botkube" >> $GITHUB_ENV

- name: Setup Go modules
id: modules
uses: ./.github/actions/setup-go-mod-private
Expand All @@ -86,15 +86,13 @@ runs:
SLACK_TESTER_TESTER_BOT_TOKEN: ${{ inputs.slack_tester_bot_token }}
SLACK_BOT_DISPLAY_NAME: ${{ inputs.slack_bot_display_name }}
SLACK_TESTER_BOT_NAME: ${{ inputs.slack_tester_bot_name }}
SLACK_TESTER_MESSAGE_WAIT_TIMEOUT: 90s
SLACK_TESTER_MESSAGE_WAIT_TIMEOUT: 180s


BOTKUBE_CLOUD_API_BASE_URL: ${{ inputs.botkube_cloud_api_base_url }}
BOTKUBE_CLOUD_EMAIL: ${{ inputs.botkube_cloud_email }}
BOTKUBE_CLOUD_PASSWORD: ${{ inputs.botkube_cloud_password }}
BOTKUBE_CLOUD_TEAM_ORGANIZATION_ID: ${{ inputs.botkube_cloud_team_organization_id }}
BOTKUBE_CLOUD_FREE_ORGANIZATION_ID: ${{ inputs.botkube_cloud_free_organization_id }}
BOTKUBE_CLOUD_PLUGIN_REPO_URL: ${{ inputs.botkube_cloud_plugin_repo_url }}
SCREENSHOTS_DIR: ${{ runner.temp }}/screenshots
DEBUG_MODE: "true"
run: |
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/branch-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,6 @@ jobs:
cloud-slack-dev-e2e:
name: Botkube Cloud Slack Dev E2E
runs-on: ubuntu-latest
needs: [ build ]
permissions:
contents: read
packages: read
Expand All @@ -316,8 +315,6 @@ jobs:
botkube_cloud_email: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_EMAIL }}
botkube_cloud_password: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_PASSWORD }}
botkube_cloud_team_organization_id: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_TEAM_ORGANIZATION_ID }}
botkube_cloud_free_organization_id: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_FREE_ORGANIZATION_ID }}
botkube_cloud_plugin_repo_url: "https://storage.googleapis.com/botkube-plugins-latest/plugins-dev-index.yaml"

slack_alerts_webhook: ${{ secrets.SLACK_CI_ALERTS_WEBHOOK }}

Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/prod-e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ jobs:
botkube_cloud_email: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_EMAIL }}
botkube_cloud_password: ${{ secrets.E2E_DEV_BOTKUBE_CLOUD_PASSWORD }}
botkube_cloud_team_organization_id: ${{ secrets.E2E_PROD_BOTKUBE_CLOUD_TEAM_ORGANIZATION_ID }}
botkube_cloud_free_organization_id: ${{ secrets.E2E_PROD_BOTKUBE_CLOUD_FREE_ORGANIZATION_ID }}
botkube_cloud_plugin_repo_url: "https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"

slack_alerts_webhook: ${{ secrets.SLACK_CI_ALERTS_WEBHOOK }}

Expand Down
249 changes: 249 additions & 0 deletions test/cloud-slack-dev-e2e/botkube_page_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
//go:build cloud_slack_dev_e2e

package cloud_slack_dev_e2e

import (
"fmt"
"net/http"
"net/url"
"os/exec"
"strings"
"testing"
"time"

"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/input"
"github.com/go-rod/rod/lib/proto"
"github.com/mattn/go-shellwords"
"github.com/stretchr/testify/require"

gqlModel "github.com/kubeshop/botkube-cloud/botkube-cloud-backend/pkg/graphql"
)

const (
authHeaderName = "Authorization"
awaitInstanceStatusChange = 2 * time.Minute
orgQueryParam = "organizationId"
)

type BotkubeCloudPage struct {
cfg E2ESlackConfig
page *Page

AuthHeaderValue string
GQLEndpoint string
ConnectedDeploy *gqlModel.Deployment
}

func NewBotkubeCloudPage(t *testing.T, cfg E2ESlackConfig) *BotkubeCloudPage {
return &BotkubeCloudPage{
page: &Page{t: t, cfg: cfg},
cfg: cfg,
GQLEndpoint: fmt.Sprintf("%s/%s", cfg.BotkubeCloud.APIBaseURL, cfg.BotkubeCloud.APIGraphQLEndpoint),
}
}

func (p *BotkubeCloudPage) NavigateAndLogin(t *testing.T, page *rod.Page) {
t.Log("Log into Botkube Cloud Dashboard")

p.page.Page = page

p.page.MustNavigate(appendOrgIDQueryParam(t, p.cfg.BotkubeCloud.UIBaseURL, p.cfg.BotkubeCloud.TeamOrganizationID))
p.page.MustWaitNavigation()

p.page.MustElement(`input[name="username"]`).MustInput(p.cfg.BotkubeCloud.Email)
p.page.MustElement(`input[name="password"]`).MustInput(p.cfg.BotkubeCloud.Password)
p.page.MustElementR("button", "^Continue$").MustClick()
p.page.Screenshot()
}

func (p *BotkubeCloudPage) HideCookieBanner(t *testing.T) {
t.Log("Hide Botkube cookie banner")
p.page.MustElementR("button", "^Decline$").MustClick()
p.page.Screenshot()
}

func (p *BotkubeCloudPage) CaptureBearerToken(t *testing.T, browser *rod.Browser) func() {
t.Logf("Starting hijacking requests to %q to get the bearer token...", p.GQLEndpoint)

router := browser.HijackRequests()
router.MustAdd(p.GQLEndpoint, func(ctx *rod.Hijack) {
if p.AuthHeaderValue != "" {
ctx.ContinueRequest(&proto.FetchContinueRequest{})
return
}

if ctx.Request != nil && ctx.Request.Method() != http.MethodPost {
ctx.ContinueRequest(&proto.FetchContinueRequest{})
return
}

require.NotNil(t, ctx.Request)
p.AuthHeaderValue = ctx.Request.Header(authHeaderName)
ctx.ContinueRequest(&proto.FetchContinueRequest{})
})
go router.Run()
return router.MustStop
}

func (p *BotkubeCloudPage) CreateNewInstance(t *testing.T, name string) {
t.Log("Create new Botkube Instance")

p.page.MustElement("h6#create-instance").MustClick()
p.page.MustElement(`input[name="name"]`).MustSelectAllText().MustInput(name)
p.page.Screenshot()

// persist connected deploy info
_, id, _ := strings.Cut(p.page.MustInfo().URL, "add/")
p.ConnectedDeploy = &gqlModel.Deployment{
Name: name,
ID: id,
}
}

func (p *BotkubeCloudPage) InstallAgentInCluster(t *testing.T, botkubeBinary string) {
t.Log("Getting Botkube install command")
installCmd := p.page.MustElement("div#install-upgrade-cmd > kbd").MustText()

t.Log("Installing Botkube using Botkube CLI")
args, err := shellwords.Parse(installCmd)
args = append(args, "--auto-approve")
require.NoError(t, err)

cmd := exec.Command(botkubeBinary, args[1:]...)
installOutput, err := cmd.CombinedOutput()
t.Log(string(installOutput))
require.NoError(t, err)

p.page.MustElement("button#cluster-connected").MustClick()
}

func (p *BotkubeCloudPage) OpenSlackAppIntegrationPage(t *testing.T) {
t.Log("Opening Slack App Integration Page")
p.page.MustElement(`button[aria-label="Add tab"]`).MustClick()
p.page.MustWaitStable()
p.page.MustElementR("button", "^Slack$").MustClick()
p.page.MustWaitStable()
p.page.Screenshot()

p.page.MustElementR("a", "Add to Slack").MustClick()
}

// ReAddSlackPlatformIfShould add the slack platform again as the page was often not refreshed with a newly connected Slack Workspace.
// It only occurs with headless mode.
func (p *BotkubeCloudPage) ReAddSlackPlatformIfShould(t *testing.T, isHeadless bool) {
if !isHeadless {
return
}

t.Log("Re-adding Slack platform")

p.page.MustActivate()
p.page.MustElement(`button[aria-label="remove"]`).MustClick()
p.page.MustElement(`button[aria-label="Add tab"]`).MustClick()
p.page.MustElementR("button", "^Slack$").MustClick()
p.page.Screenshot()
}

func (p *BotkubeCloudPage) VerifyDeploymentStatus(t *testing.T, status string) {
t.Logf("Waiting for status '%s'", status)
p.page.Timeout(awaitInstanceStatusChange).MustElementR("div#deployment-status", status)
}

func (p *BotkubeCloudPage) SetupSlackWorkspace(t *testing.T, channel string) {
t.Logf("Selecting newly connected %q Slack Workspace", p.cfg.Slack.WorkspaceName)

p.page.MustElement(`input[type="search"]`).
MustInput(p.cfg.Slack.WorkspaceName).
MustType(input.Enter)
p.page.Screenshot()

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

t.Log("Selecting channel checkbox")
p.page.MustElement("input#name-channel").MustInput(channel).MustType(input.Enter)
p.page.MustElement(fmt.Sprintf(`input[type="checkbox"][name="%s"]`, channel)).MustClick()
p.page.Screenshot()
}

func (p *BotkubeCloudPage) FinishWizard(t *testing.T) {
t.Log("Navigating to plugin selection")
p.page.Screenshot("before-first-next")

p.page.MustElementR("button", "/^Next$/i").
MustWaitEnabled().
// We need to wait, otherwise, we click the same 'Next' button twice before the query is executed, and we are not really
// moved to the next step. Updating the navigation would resolve that issue.
MustClick().MustWaitStable()

p.page.Screenshot("after-first-next")

t.Log("Using pre-selected plugins. Navigating to wizard summary")
p.page.MustElementR("button", "/^Next$/i").
MustWaitEnabled().
// We need to wait, otherwise, we click the same 'Next' button twice before the query is executed, and we are not really
// moved to the next step. Updating the navigation would resolve that issue.
MustClick().MustWaitStable()
p.page.Screenshot("after-second-next")

t.Log("Submitting changes")
p.page.MustElementR("button", "/^Deploy changes$/i").
MustWaitEnabled().
MustClick()
p.page.Screenshot("after-deploy-changes")

// wait till gql mutation passes, and navigates to install details, otherwise, we could navigate to instance details with state 'draft'
p.page.MustWaitNavigation()
p.page.Screenshot("after-deploy-changes-navigation")
}

func (p *BotkubeCloudPage) UpdateKubectlNamespace(t *testing.T) {
t.Log("Updating 'kubectl' namespace property")

p.openKubectlUpdateForm()

p.page.MustElementR("input#root_defaultNamespace", "default").MustSelectAllText().MustInput("kube-system")
p.page.Screenshot("after-changing-namespace-property")
p.page.MustElementR("button", "/^Update$/i").MustClick()
p.page.Screenshot("after-clicking-plugin-update")

t.Log("Submitting changes")
p.page.MustWaitStable()
p.page.MustElementR("button", "/Deploy changes/i").MustClick()
p.page.Screenshot("after-deploying-plugin-changes")
}

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

p.openKubectlUpdateForm()
p.page.MustElementR("input#root_defaultNamespace", "kube-system")
}

func (p *BotkubeCloudPage) openKubectlUpdateForm() {
p.page.Screenshot("before-selecting-plugins-tab")
p.page.MustElementR(`div[role="tab"]`, "Plugins").MustFocus().MustClick().MustWaitStable()

p.page.MustWaitStable()
p.page.Screenshot("after-selecting-plugins-tab")

p.page.MustElement(`button[id^="botkube/kubectl_"]`).MustClick()
p.page.Screenshot("after-opening-kubectl-cfg")

p.page.MustElement(`div[data-node-key="ui-form"]`).MustClick()
p.page.Screenshot("after-selecting-kubectl-cfg-form")
}

func appendOrgIDQueryParam(t *testing.T, inURL, orgID string) string {
parsedURL, err := url.Parse(inURL)
require.NoError(t, err)
queryValues := parsedURL.Query()
queryValues.Set(orgQueryParam, orgID)
parsedURL.RawQuery = queryValues.Encode()

return parsedURL.String()
}
Loading
Loading