Skip to content
This repository was archived by the owner on Oct 3, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7486686
test-ui-devperspective: Create UI test to verify Developer perspective
pmacik May 14, 2019
834f711
test-ui-devperspective: Add github.com/tebeka/selenium to Gopkg.*
pmacik May 14, 2019
da10548
test-ui-devperspective: Update Gopkg.* with latest github.com/tebeka/…
pmacik May 14, 2019
2fc8f73
test-ui-devperspective: Update steps before test.
pmacik May 14, 2019
97900c9
test-ui-devperspective: Fix Go lint issues.
pmacik May 14, 2019
c24debb
test-ui-devperspective: Install chromedriver and chomium-headless to …
pmacik May 15, 2019
f3fb51c
test-ui-devperspective: Set CHROMEDRIVER_BINARY to full path
pmacik May 15, 2019
26a1349
test-ui-devperspective: Set CHROMEDRIVER_BINARY to full path as a def…
pmacik May 15, 2019
b44f04e
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 16, 2019
93385e5
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 16, 2019
6826e6d
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 16, 2019
f37e6d3
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 17, 2019
6442a2e
test-ui-devperspective: Split test for admin and non-admin user.
pmacik May 17, 2019
744fb05
test-ui-devperspective: Updated log outputs for the console app.
pmacik May 17, 2019
df4f73c
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 20, 2019
2a5b245
test-ui-devperspective: Organize code into packages
pmacik May 20, 2019
1cb9cea
test-ui-devperspective: Exclude the UI test from unit tests.
pmacik May 20, 2019
375eff9
test-ui-devperspective: Include the UI test in e2e tests.
pmacik May 20, 2019
83a06e5
test-ui-devperspective: Implement re-tries for FindElementBy func and…
pmacik May 21, 2019
13f734a
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 21, 2019
97f867a
test-ui-devperspective: Harden the condition for FindElementBy function.
pmacik May 21, 2019
2be9b3e
test-ui-devperspective: Fail test if for element is not found in Find…
pmacik May 21, 2019
65c0827
test-ui-devperspective: Make test more verbose on what is doing.
pmacik May 21, 2019
5faff40
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 21, 2019
171fe19
test-ui-devperspective: Remove the test from e2e tests. Let's keep th…
pmacik May 22, 2019
cfc022f
Merge branch 'master' into odc-807_test-ui-devperspective
pmacik May 27, 2019
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
23 changes: 23 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ required = [
"k8s.io/gengo/args",
"sigs.k8s.io/controller-tools/pkg/crd/generator",
"github.com/golang/protobuf/proto",
"github.com/tebeka/selenium",
]

[[override]]
Expand Down Expand Up @@ -107,3 +108,7 @@ required = [
"pkg/apis",
"pkg/apis/devconsole/v1alpha1",
]

[[constraint]]
name = "github.com/tebeka/selenium"
branch = "master"
30 changes: 27 additions & 3 deletions make/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export DEPLOYED_NAMESPACE:=
.PHONY: test
## Runs Go package tests and stops when the first one fails
test: ./vendor
$(Q)go test -vet off ${V_FLAG} $(shell go list ./... | grep -v -E '(/test/e2e|/test/operatorsource)') -failfast
$(Q)go test -vet off ${V_FLAG} $(shell go list ./... | grep -v -E '(/test/e2e|/test/operatorsource|/test/ui)') -failfast

.PHONY: test-coverage
## Runs Go package tests and produces coverage information
Expand All @@ -26,7 +26,7 @@ test-coverage-html: ./vendor ./out/cover.out
$(Q)go tool cover -html=./out/cover.out

./out/cover.out: ./vendor
$(Q)go test ${V_FLAG} -race $(shell go list ./... | grep -v -E '(/test/e2e|/test/operatorsource)') -failfast -coverprofile=cover.out -covermode=atomic -outputdir=./out
$(Q)go test ${V_FLAG} -race $(shell go list ./... | grep -v -E '(/test/e2e|/test/operatorsource|/test/ui)') -failfast -coverprofile=cover.out -covermode=atomic -outputdir=./out

.PHONY: get-test-namespace
get-test-namespace: ./out/test-namespace
Expand All @@ -49,7 +49,6 @@ ifeq ($(OPENSHIFT_VERSION),3)
endif
$(Q)operator-sdk test local ./test/e2e --namespace $(TEST_NAMESPACE) --up-local --go-test-flags "-v -timeout=15m"


.PHONY: e2e-setup
e2e-setup: e2e-cleanup
$(Q)oc new-project $(TEST_NAMESPACE)
Expand Down Expand Up @@ -112,6 +111,31 @@ test-operator-source: push-operator-app-registry
DEVCONSOLE_OPERATOR_VERSION=$(DEVCONSOLE_OPERATOR_VERSION) \
go test -vet off ${V_FLAG} $(shell go list ./... | grep $(OPSRC_DIR)) -failfast

.PHONY: test-ui-devperspective-admin
test-ui-devperspective-admin:
$(Q)oc login -u system:admin
$(Q)sh ./hack/install_devconsole/consoledeveloper.sh
$(eval export DEVCONSOLE_USERNAME := $(OC_LOGIN_USERNAME))
$(eval export DEVCONSOLE_PASSWORD := $(OC_LOGIN_PASSWORD))
$(eval export CHROMEDRIVER_BINARY := /usr/bin/chromedriver)
$(eval CONSOLE_TARGET_PORT := $(shell oc get routes console -n openshift-console -o jsonpath='{.spec.port.targetPort}'))
$(eval CONSOLE_HOST := $(shell oc get routes console -n openshift-console -o jsonpath='{.spec.host}'))
$(eval export OS_CONSOLE_URL := $(CONSOLE_TARGET_PORT)://$(CONSOLE_HOST))
$(Q)go test -vet off ${V_FLAG} $(shell go list ./... | grep test/ui/devperspective) -failfast

.PHONY: test-ui-devperspective-nonadmin
test-ui-devperspective-nonadmin:
$(Q)oc login -u system:admin
$(Q)sh ./hack/install_devconsole/consoledeveloper.sh
$(eval export DEVCONSOLE_USERNAME := consoledeveloper)
$(eval export DEVCONSOLE_PASSWORD := developer)
$(eval export CHROMEDRIVER_BINARY := /usr/bin/chromedriver)
$(eval CONSOLE_TARGET_PORT := $(shell oc get routes console -n openshift-console -o jsonpath='{.spec.port.targetPort}'))
$(eval CONSOLE_HOST := $(shell oc get routes console -n openshift-console -o jsonpath='{.spec.host}'))
$(eval export OS_CONSOLE_URL := $(CONSOLE_TARGET_PORT)://$(CONSOLE_HOST))
$(eval export USER_IS_ADMIN := false)
$(Q)go test -vet off ${V_FLAG} $(shell go list ./... | grep test/ui/devperspective) -failfast

.PHONY: olm-integration-cleanup
olm-integration-cleanup: get-test-namespace
ifeq ($(OPENSHIFT_VERSION),3)
Expand Down
20 changes: 20 additions & 0 deletions test/support/support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package support

import (
"os"
"testing"
)

// Getenv returns a value of environment variable, if it exists.
// Returns the default value otherwise.
func Getenv(t *testing.T, key string, defaultValue string) string {
value, found := os.LookupEnv(key)
var retVal string
if found {
retVal = value
} else {
retVal = defaultValue
}
t.Logf("Using env variable: %s=%s", key, retVal)
return retVal
}
102 changes: 102 additions & 0 deletions test/ui/devperspective/devperspective_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package devperspective

import (
"fmt"
"strconv"
"testing"
"time"

"github.com/redhat-developer/devconsole-operator/test/support"
"github.com/redhat-developer/devconsole-operator/test/ui"
"github.com/stretchr/testify/require"

"github.com/tebeka/selenium"
)

var tag string

func TestDevPerspective(t *testing.T) {
tag = support.Getenv(t, "TAG", fmt.Sprintf("%d", time.Now().Unix()))
userIsAdmin := support.Getenv(t, "USER_IS_ADMIN", "true")
chBin := support.Getenv(t, "CHROMEDRIVER_BINARY", "/usr/bin/chromedriver")
chPort, err := strconv.Atoi(support.Getenv(t, "CHROMEDRIVER_PORT", "9515"))
require.NoError(t, err, "Chromedriver port")

devconsoleUsername := support.Getenv(t, "DEVCONSOLE_USERNAME", "consoledeveloper")
devconsolePassword := support.Getenv(t, "DEVCONSOLE_PASSWORD", "developer")
openshiftConsoleURL := support.Getenv(t, "OS_CONSOLE_URL", "http://localhost")

wd, svc := ui.InitSelenium(
t,
chBin,
chPort,
)

defer tearDown(t, wd, svc)

defaultWait := 10 * time.Second

t.Logf("Open URL: %s", openshiftConsoleURL)
err = wd.Get(openshiftConsoleURL)
require.NoErrorf(t, err, "Open URL: %s", openshiftConsoleURL)
consoleIsUp := false
for attempt := 0; attempt < 10; attempt++ {
err = wd.Refresh()
require.NoErrorf(t, err, "Refresh URL: %s", openshiftConsoleURL)
el, _ := wd.FindElement(selenium.ByXPATH, "//*[contains(text(),'Application is not available')]")
if el != nil {
t.Logf("Openshift Console is not available, try again after 2s.")
time.Sleep(2 * time.Second)
} else {
t.Logf("Openshift Console is up.")
consoleIsUp = true
break
}
}
if !consoleIsUp {
require.FailNow(t, "Openshift Console is not available.")
}

require.NoError(t, err, fmt.Sprintf("Open console starting URL: %s", openshiftConsoleURL))
ui.WaitForURLToContain(t, wd, "oauth", defaultWait)

var elem selenium.WebElement

if userIsAdmin == "true" {
elem = ui.FindElementBy(t, wd, selenium.ByLinkText, "kube:admin")
} else {
elem = ui.FindElementBy(t, wd, selenium.ByLinkText, devconsoleUsername)
}

ui.WaitForElementToBeDisplayed(t, wd, elem, defaultWait)
ui.ClickToElement(t, elem)

elem = ui.FindElementBy(t, wd, selenium.ByID, "inputUsername")
ui.WaitForElementToBeDisplayed(t, wd, elem, defaultWait)
ui.SendKeysToElement(t, elem, devconsoleUsername)

elem = ui.FindElementBy(t, wd, selenium.ByID, "inputPassword")
ui.WaitForElementToBeDisplayed(t, wd, elem, defaultWait)
ui.SendKeysToElement(t, elem, devconsolePassword)

elem = ui.FindElementBy(t, wd, selenium.ByXPATH, "//*/button[contains(text(),'Log In')]")
ui.ClickToElement(t, elem)

elem = ui.FindElementBy(t, wd, selenium.ByID, "nav-toggle")
ui.WaitForElementToBeDisplayed(t, wd, elem, defaultWait)
ui.ClickToElement(t, elem)

elem = ui.FindElementBy(t, wd, selenium.ByXPATH, "//*/a[@target][contains(text(),'Developer')]")
ui.WaitForElementToBeDisplayed(t, wd, elem, defaultWait)
}

func tearDown(t *testing.T, wd selenium.WebDriver, svc *selenium.Service) {
err := wd.Quit()
if err != nil {
t.Log(err)
}
err = svc.Stop()
if err != nil {
t.Log(err)
}
}
131 changes: 131 additions & 0 deletions test/ui/selenium.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package ui

import (
"bytes"
"fmt"
"image"
"image/png"
"os"
"path"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/tebeka/selenium"
)

//FindElementBy look for a web element by a given selector and returs it back when found.
Copy link
Contributor

@ldimaggi ldimaggi May 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion - I have noticed that there are intermittent timing issues related to the appearance of the UI elements in the Developer Perspective in the UI - I assume that this is due to the DOM being updated.

It would be a good idea for the test to be robust enough to avoid 'element not fund' errors. I'd suggest making this change:

// MaxCount = Max count for loop used to discover UI elements
const MaxCount = 10

//FindElementBy look for a web element by a given selector and returs it back when found.
func FindElementBy(t *testing.T, wd selenium.WebDriver, by string, selector string) selenium.WebElement {

	var elems []selenium.WebElement
	elems, err := wd.FindElements(by, selector)

	// To avoid a problem where the element is yet not present
	counter := 0
	for {
		if len(elems) > 0 || counter > MaxCount {
			break
		}
		counter++
		time.Sleep(100 * time.Millisecond)
	}

	elem, err := wd.FindElement(by, selector)
	require.NoError(t, err, fmt.Sprintf("Find element by %s=%s", by, selector))
	return elem
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ldimaggi Thanks for that idea. I implemented it in 83a06e5

func FindElementBy(t *testing.T, wd selenium.WebDriver, by string, selector string) selenium.WebElement {
t.Logf("Find element by %s=%s", by, selector)
maxAttempts := 10
attemptInterval := 100 * time.Millisecond

// To avoid a problem where the element is yet not present
counter := 0
for {
elems, err := wd.FindElements(by, selector)
if err != nil || len(elems) == 0 {
if counter <= maxAttempts {
t.Logf("Element for %s=%s not found, trying again...", by, selector)
time.Sleep(attemptInterval)
counter++
} else {
require.NoError(t, fmt.Errorf("element for %s=%s not found", by, selector))
}
} else {
return elems[0]
}
}
}

//WaitForElementToBeDisplayed for a given web element to be displayed/visible for a given time duration.
func WaitForElementToBeDisplayed(t *testing.T, wd selenium.WebDriver, element selenium.WebElement, duration time.Duration) {
t.Logf("Wait for element to be displayed")
err := wd.WaitWithTimeout(func(wd selenium.WebDriver) (bool, error) {
return element.IsDisplayed()
}, duration)
require.NoError(t, err, "Wait for element to be displayed")
}

//WaitForURLToContain waits for a current URL to contain the given text. It waits for a given time duration.
func WaitForURLToContain(t *testing.T, wd selenium.WebDriver, text string, duration time.Duration) {
t.Logf("Wait for URL to contain test '%s'...", text)
counter := 1
err := wd.WaitWithTimeout(func(wd selenium.WebDriver) (bool, error) {
currentURL, err2 := wd.CurrentURL()
counter++
return strings.Contains(currentURL, text), err2
}, duration)
currentURL, err2 := wd.CurrentURL()
require.NoError(t, err2, fmt.Sprintf("Get current URL"))
require.NoError(t, err, fmt.Sprintf("Wait for URL to contain '%s'. The current URL is '%s'.", text, currentURL))
}

//SendKeysToElement sends keys to a given web element
func SendKeysToElement(t *testing.T, element selenium.WebElement, keys string) {
t.Log("Send keys to element")
err := element.SendKeys(keys)
require.NoError(t, err, "Send keys to element")
}

//ClickToElement performs a click on a given web element.
func ClickToElement(t *testing.T, element selenium.WebElement) {
t.Log("Click to element")
err := element.Click()
require.NoError(t, err, "Click to element")
}

//InitSelenium creates and initializes a new ChromeDriver service.
func InitSelenium(t *testing.T, chromedriverPath string, chromedriverPort int) (selenium.WebDriver, *selenium.Service) {

service, err := selenium.NewChromeDriverService(chromedriverPath, chromedriverPort)
require.NoError(t, err)

chromeOptions := map[string]interface{}{
"args": []string{
"--verbose",
"--no-cache",
"--no-sandbox",
"--headless",
"--window-size=1920,1080",
"--window-position=0,0",
"--enable-features=NetworkService", // to ignore invalid HTTPS certificates
//"--whitelisted-ips=''", // to support running in a container
},
}

caps := selenium.Capabilities{
"browserName": "chrome",
"chromeOptions": chromeOptions,
}

wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", chromedriverPort))
require.NoError(t, err)
return wd, service
}

//SaveScreenShotToPNG saves current screen to a given PNG file.
func SaveScreenShotToPNG(t *testing.T, wd selenium.WebDriver, filename string) {
t.Logf("Save screenshot to '%s'", filename)
err := os.MkdirAll(path.Dir(filename), 0775)
require.NoError(t, err, "Create screenshot directory")
// convert []byte to image for saving to file
imgByte, err := wd.Screenshot()
require.NoError(t, err, "Take screenshot")
img, _, _ := image.Decode(bytes.NewReader(imgByte))

//save the imgByte to file
out, err := os.Create(filename)
defer close(t, out)

require.NoError(t, err, "Create a file for the screenshot")

err = png.Encode(out, img)
require.NoError(t, err, "Write screanshot to PNG file")
}

func close(t *testing.T, f *os.File) {
err := f.Close()
require.NoError(t, err, "Close output file")
}