Skip to content

Commit

Permalink
Add early cleanup for Slack E2E tests (#1463)
Browse files Browse the repository at this point in the history
  • Loading branch information
pkosiec authored Jun 24, 2024
1 parent 3f04c2a commit 6a95f5e
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 82 deletions.
38 changes: 31 additions & 7 deletions test/cloud-slack-dev-e2e/botkube_page_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package cloud_slack_dev_e2e

import (
"botkube.io/botube/test/cloud_graphql"
"fmt"
"net/http"
"net/url"
Expand Down Expand Up @@ -33,6 +34,7 @@ type BotkubeCloudPage struct {
AuthHeaderValue string
GQLEndpoint string
ConnectedDeploy *gqlModel.Deployment
AlreadyDeleted bool
}

func NewBotkubeCloudPage(t *testing.T, cfg E2ESlackConfig) *BotkubeCloudPage {
Expand Down Expand Up @@ -63,7 +65,7 @@ func (p *BotkubeCloudPage) HideCookieBanner(t *testing.T) {
p.page.Screenshot()
}

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

router := browser.HijackRequests()
Expand All @@ -80,6 +82,7 @@ func (p *BotkubeCloudPage) CaptureBearerToken(t *testing.T, browser *rod.Browser

require.NotNil(t, ctx.Request)
p.AuthHeaderValue = ctx.Request.Header(authHeaderName)
t.Log("Bearer token intercepted")
ctx.ContinueRequest(&proto.FetchContinueRequest{})
})
go router.Run()
Expand All @@ -90,8 +93,10 @@ func (p *BotkubeCloudPage) CreateNewInstance(t *testing.T, name string) {
t.Log("Create new Botkube Instance")

p.page.MustElement("h6#create-instance").MustClick()
time.Sleep(3 * time.Second)
p.page.Screenshot("after-clicking-create-instance")
p.page.MustElement(`input[name="name"]`).MustSelectAllText().MustInput(name)
p.page.Screenshot()
p.page.Screenshot("after-filling-in-instance-name")

// persist connected deploy info
_, id, _ := strings.Cut(p.page.MustInfo().URL, "add/")
Expand Down Expand Up @@ -152,22 +157,23 @@ func (p *BotkubeCloudPage) VerifyDeploymentStatus(t *testing.T, status string) {

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

time.Sleep(3 * time.Second)
p.page.Screenshot("before-selecting-workspace")
p.page.MustElement(`input[type="search"]`).
MustInput(p.cfg.Slack.WorkspaceName).
MustType(input.Enter)
p.page.Screenshot()
p.page.Screenshot("after-selecting-workspace")

// 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.Screenshot("before-filtering-channel")
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()
p.page.Screenshot("after-selecting-channel")
}

func (p *BotkubeCloudPage) FinishWizard(t *testing.T) {
Expand All @@ -193,6 +199,7 @@ func (p *BotkubeCloudPage) FinishWizard(t *testing.T) {
p.page.Screenshot("after-second-next")

t.Log("Submitting changes")
p.page.Mouse.MustMoveTo(0, 0)
time.Sleep(3 * time.Second)
p.page.MustElementR("button", "/^Deploy changes$/i").
MustWaitEnabled().
Expand All @@ -216,10 +223,11 @@ func (p *BotkubeCloudPage) UpdateKubectlNamespace(t *testing.T) {

t.Log("Moving to top left corner of the page")
p.page.Mouse.MustMoveTo(0, 0)
p.page.Screenshot("after-moving-to-top-left")
time.Sleep(3 * time.Second)

t.Log("Submitting changes")
p.page.MustWaitStable()
p.page.Screenshot("before-deploying-plugin-changes")
p.page.MustElementR("button", "/Deploy changes/i").MustClick()
p.page.Screenshot("after-deploying-plugin-changes")
}
Expand Down Expand Up @@ -247,6 +255,22 @@ func (p *BotkubeCloudPage) openKubectlUpdateForm() {
p.page.Screenshot("after-selecting-kubectl-cfg-form")
}

func (p *BotkubeCloudPage) Cleanup(t *testing.T, gqlCli *cloud_graphql.Client) {
if p.AlreadyDeleted {
return
}

t.Log("Cleaning up Botkube instance on test failure...")

if p.ConnectedDeploy == nil {
t.Log("No deployment to delete")
return
}

deleteDeployment(t, gqlCli, p.ConnectedDeploy.ID, "connected")
p.AlreadyDeleted = true
}

func appendOrgIDQueryParam(t *testing.T, inURL, orgID string) string {
parsedURL, err := url.Parse(inURL)
require.NoError(t, err)
Expand Down
185 changes: 112 additions & 73 deletions test/cloud-slack-dev-e2e/cloud_slack_dev_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type BotkubeCloudConfig struct {
}

func TestCloudSlackE2E(t *testing.T) {
t.Log("Loading configuration...")
t.Log("1. Loading configuration...")
var cfg E2ESlackConfig
err := envconfig.Init(&cfg)
require.NoError(t, err)
Expand Down Expand Up @@ -122,97 +122,82 @@ func TestCloudSlackE2E(t *testing.T) {
botkubeCloudPage := NewBotkubeCloudPage(t, cfg)
slackPage := NewSlackPage(t, cfg)

t.Run("Creating Botkube Instance with newly added Slack Workspace", func(t *testing.T) {
t.Log("Setting up browser...")
t.Log("2. Creating Botkube Instance with newly added Slack Workspace")

launcher := launcher.New().Headless(true)
isHeadless := launcher.Has(flags.Headless)
t.Cleanup(launcher.Cleanup)
t.Log("Setting up browser...")
launcher := launcher.New().Headless(true)
isHeadless := launcher.Has(flags.Headless)
t.Cleanup(launcher.Cleanup)

browser := rod.New().Trace(cfg.DebugMode).ControlURL(launcher.MustLaunch()).MustConnect()
t.Cleanup(func() {
err := browser.Close()
if err != nil {
t.Logf("Failed to close browser: %v", err)
}
})
browser := rod.New().Trace(cfg.DebugMode).ControlURL(launcher.MustLaunch()).MustConnect()
t.Cleanup(func() {
err := browser.Close()
if err != nil {
t.Logf("Failed to close browser: %v", err)
}
})

page := newBrowserPage(t, browser, cfg)
t.Cleanup(func() {
closePage(t, "page", page)
})
page := newBrowserPage(t, browser, cfg)
t.Cleanup(func() {
closePage(t, "page", page)
})

botkubeCloudPage.NavigateAndLogin(t, page)
botkubeCloudPage.HideCookieBanner(t)
stopRouter := botkubeCloudPage.InterceptBearerToken(t, browser)
defer stopRouter()

stopRouter := botkubeCloudPage.CaptureBearerToken(t, browser)
defer stopRouter()
botkubeCloudPage.NavigateAndLogin(t, page)
botkubeCloudPage.HideCookieBanner(t)

botkubeCloudPage.CreateNewInstance(t, channel.Name())
botkubeCloudPage.InstallAgentInCluster(t, cfg.BotkubeCliBinaryPath)
botkubeCloudPage.OpenSlackAppIntegrationPage(t)
botkubeCloudPage.CreateNewInstance(t, channel.Name())
t.Cleanup(func() {
// Delete Botkube instance.
// Cleanup is skipped if the instance was already deleted.
// This cleanup is needed if there's a fail between instance creation and Slack workspace connection.
gqlCli := createGQLCli(t, cfg, botkubeCloudPage)
botkubeCloudPage.Cleanup(t, gqlCli)
})
botkubeCloudPage.InstallAgentInCluster(t, cfg.BotkubeCliBinaryPath)
botkubeCloudPage.OpenSlackAppIntegrationPage(t)

slackPage.ConnectWorkspace(t, browser)
slackPage.ConnectWorkspace(t, browser)
t.Cleanup(func() {
// Disconnect Slack workspace.
gqlCli := createGQLCli(t, cfg, botkubeCloudPage)
slackPage.Cleanup(t, gqlCli)
})
t.Cleanup(func() {
// Delete Botkube instance.
// The code is repeated on purpose: we want to make sure the instance is cleaned up before the Slack workspace.
// t.Cleanup functions are called in last added, first called order.
gqlCli := createGQLCli(t, cfg, botkubeCloudPage)
botkubeCloudPage.Cleanup(t, gqlCli)
})

botkubeCloudPage.ReAddSlackPlatformIfShould(t, isHeadless)
botkubeCloudPage.SetupSlackWorkspace(t, channel.Name())
botkubeCloudPage.FinishWizard(t)
botkubeCloudPage.VerifyDeploymentStatus(t, "Connected")
botkubeCloudPage.ReAddSlackPlatformIfShould(t, isHeadless)
botkubeCloudPage.SetupSlackWorkspace(t, channel.Name())
botkubeCloudPage.FinishWizard(t)
botkubeCloudPage.VerifyDeploymentStatus(t, "Connected")

botkubeCloudPage.UpdateKubectlNamespace(t)
botkubeCloudPage.VerifyDeploymentStatus(t, "Updating")
botkubeCloudPage.VerifyDeploymentStatus(t, "Connected")
botkubeCloudPage.VerifyUpdatedKubectlNamespace(t)
})
botkubeCloudPage.UpdateKubectlNamespace(t)
botkubeCloudPage.VerifyDeploymentStatus(t, "Updating")
botkubeCloudPage.VerifyDeploymentStatus(t, "Connected")
botkubeCloudPage.VerifyUpdatedKubectlNamespace(t)

t.Run("Run E2E tests with deployment", func(t *testing.T) {
gqlCli := createGQLCli(t, cfg, botkubeCloudPage)

connectedDeploy := botkubeCloudPage.ConnectedDeploy
require.NotNil(t, connectedDeploy, "Previous subtest needs to pass to get connected deployment information")
require.NotEmpty(t, botkubeCloudPage.AuthHeaderValue, "Previous subtest needs to pass to get authorization header value")
// cleanup is done in the upper test function

t.Logf("Using Organization ID %q and Authorization header starting with %q", cfg.BotkubeCloud.TeamOrganizationID,
stringsutil.ShortenString(botkubeCloudPage.AuthHeaderValue, 15))

gqlCli := cloud_graphql.NewClientForAuthAndOrg(botkubeCloudPage.GQLEndpoint, cfg.BotkubeCloud.TeamOrganizationID, botkubeCloudPage.AuthHeaderValue)

t.Logf("Getting connected Slack workspace...")
slackWorkspaces := gqlCli.MustListSlackWorkspacesForOrg(t, cfg.BotkubeCloud.TeamOrganizationID)
require.Len(t, slackWorkspaces, 1)
slackWorkspace := slackWorkspaces[0]
slackWorkspace := findConnectedSlackWorkspace(t, cfg, gqlCli)
require.NotNil(t, slackWorkspace)
t.Cleanup(func() {
if !cfg.Slack.DisconnectWorkspaceAfterTests {
return
}
t.Log("Disconnecting Slack workspace...")
err = retryOperation(func() error {
return gqlCli.DeleteSlackWorkspace(t, cfg.BotkubeCloud.TeamOrganizationID, slackWorkspace.ID)
})
if err != nil {
t.Logf("Failed to disconnect Slack workspace: %s", err.Error())
}
})
// cleanup is done in the upper test function

t.Log("Creating a second deployment to test not connected flow...")
notConnectedDeploy := gqlCli.MustCreateBasicDeploymentWithCloudSlack(t, fmt.Sprintf("%s-2", channel.Name()), slackWorkspace.TeamID, channel.Name())
t.Cleanup(func() {
t.Log("Deleting second deployment...")
err = retryOperation(func() error {
return gqlCli.DeleteDeployment(t, graphql.ID(notConnectedDeploy.ID))
})
if err != nil {
t.Logf("Failed to delete second deployment: %s", err.Error())
}
})

t.Cleanup(func() {
t.Log("Deleting first deployment...")
err = retryOperation(func() error {
return gqlCli.DeleteDeployment(t, graphql.ID(connectedDeploy.ID))
})
if err != nil {
t.Logf("Failed to delete first deployment: %s", err.Error())
}
deleteDeployment(t, gqlCli, notConnectedDeploy.ID, "second (not connected)")
})

t.Log("Waiting for help message...")
Expand Down Expand Up @@ -511,6 +496,60 @@ func createK8sCli(t *testing.T, kubeconfigPath string) *kubernetes.Clientset {
return k8sCli
}

func createGQLCli(t *testing.T, cfg E2ESlackConfig, botkubeCloudPage *BotkubeCloudPage) *cloud_graphql.Client {
require.NotEmpty(t, botkubeCloudPage.AuthHeaderValue, "Authorization header value should be set")

t.Logf("Using Organization ID %q and Authorization header starting with %q", cfg.BotkubeCloud.TeamOrganizationID,
stringsutil.ShortenString(botkubeCloudPage.AuthHeaderValue, 15))
return cloud_graphql.NewClientForAuthAndOrg(botkubeCloudPage.GQLEndpoint, cfg.BotkubeCloud.TeamOrganizationID, botkubeCloudPage.AuthHeaderValue)
}

func findConnectedSlackWorkspace(t *testing.T, cfg E2ESlackConfig, gqlCli *cloud_graphql.Client) *gqlModel.SlackWorkspace {
t.Logf("Finding connected Slack workspace...")
slackWorkspaces := gqlCli.MustListSlackWorkspacesForOrg(t, cfg.BotkubeCloud.TeamOrganizationID)
if len(slackWorkspaces) == 0 {
return nil
}

if len(slackWorkspaces) > 1 {
t.Logf("Found multiple connected Slack workspaces: %v", slackWorkspaces)
return nil
}

slackWorkspace := slackWorkspaces[0]
return slackWorkspace
}

func disconnectConnectedSlackWorkspace(t *testing.T, cfg E2ESlackConfig, gqlCli *cloud_graphql.Client, slackWorkspace *gqlModel.SlackWorkspace) {
if slackWorkspace == nil {
t.Log("Skipping disconnecting Slack workspace as it is nil")
return
}

if !cfg.Slack.DisconnectWorkspaceAfterTests {
t.Log("Skipping disconnecting Slack workspace...")
return
}

t.Log("Disconnecting Slack workspace...")
err := retryOperation(func() error {
return gqlCli.DeleteSlackWorkspace(t, cfg.BotkubeCloud.TeamOrganizationID, slackWorkspace.ID)
})
if err != nil {
t.Logf("Failed to disconnect Slack workspace: %s", err.Error())
}
}

func deleteDeployment(t *testing.T, gqlCli *cloud_graphql.Client, deployID string, label string) {
t.Logf("Deleting %s deployment...", label)
err := retryOperation(func() error {
return gqlCli.DeleteDeployment(t, graphql.ID(deployID))
})
if err != nil {
t.Logf("Failed to delete first deployment: %s", err.Error())
}
}

func retryOperation(fn func() error) error {
return retry.Do(fn,
retry.Attempts(cleanupRetryAttempts),
Expand Down
2 changes: 1 addition & 1 deletion test/cloud-slack-dev-e2e/page_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func closePage(t *testing.T, name string, page *rod.Page) {
t.Helper()
err := page.Close()
if err != nil {
if errors.Is(err, context.Canceled) {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return
}

Expand Down
13 changes: 12 additions & 1 deletion test/cloud-slack-dev-e2e/slack_page_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package cloud_slack_dev_e2e

import (
"botkube.io/botube/test/cloud_graphql"
"context"
"errors"
"github.com/stretchr/testify/assert"
Expand All @@ -15,7 +16,6 @@ import (
const (
slackBaseURL = "slack.com"
waitTime = 10 * time.Second
contextTimeout = 30 * time.Second
shorterContextTimeout = 10 * time.Second
)

Expand Down Expand Up @@ -85,10 +85,21 @@ func (p *SlackPage) ConnectWorkspace(t *testing.T, browser *rod.Browser) {
time.Sleep(waitTime)
p.page.Screenshot("before-workspace-connect")
p.page.MustElement("button#slack-workspace-connect").MustClick()
time.Sleep(1 * time.Second)
p.page.Screenshot("after-workspace-connect")
}

t.Log("Waiting for page auto-close...")
err = p.page.WaitIdle(waitTime) // wait for auto-close
assert.NoError(t, err)
}

func (p *SlackPage) Cleanup(t *testing.T, gqlCli *cloud_graphql.Client) {
t.Log("Cleaning up Slack workspace on test failure...")
if !p.cfg.Slack.DisconnectWorkspaceAfterTests {
return
}

slackWorkspace := findConnectedSlackWorkspace(t, p.cfg, gqlCli)
disconnectConnectedSlackWorkspace(t, p.cfg, gqlCli, slackWorkspace)
}

0 comments on commit 6a95f5e

Please sign in to comment.