ContainerImage.Pinniped/test/library/browsertest/browsertest.go
Andrew Keesler c53507809d Rename dex namespace, add new ytt value to deploy/tools, and remove Tilt
- Rename the test/deploy/dex directory to test/deploy/tools
- Rename the dex namespace to tools
- Add a new ytt value called `pinny_ldap_password` for the tools
  ytt templates
- This new value is not used on main at this time. We intend to use
  it in the forthcoming ldap branch. We're defining it on main so
  that the CI scripts can use it across all branches and PRs.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2021-04-05 15:01:49 -07:00

159 lines
4.9 KiB
Go

// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package browsertest provides integration test helpers for our browser-based tests.
package browsertest
import (
"regexp"
"testing"
"time"
"github.com/sclevine/agouti"
"github.com/stretchr/testify/require"
"go.pinniped.dev/test/library"
)
const (
operationTimeout = 10 * time.Second
operationPollingInterval = 100 * time.Millisecond
)
// Open a webdriver-driven browser and returns an *agouti.Page to control it. The browser will be automatically
// closed at the end of the current test. It is configured for test purposes with the correct HTTP proxy and
// in a mode that ignore certificate errors.
func Open(t *testing.T) *agouti.Page {
t.Logf("opening browser driver")
env := library.IntegrationEnv(t)
caps := agouti.NewCapabilities()
if env.Proxy != "" {
t.Logf("configuring Chrome to use proxy %q", env.Proxy)
caps = caps.Proxy(agouti.ProxyConfig{
ProxyType: "manual",
HTTPProxy: env.Proxy,
SSLProxy: env.Proxy,
NoProxy: "127.0.0.1",
})
}
agoutiDriver := agouti.ChromeDriver(
agouti.Desired(caps),
agouti.ChromeOptions("args", []string{
"--no-sandbox",
"--ignore-certificate-errors",
"--headless", // Comment out this line to see the tests happen in a visible browser window.
}),
// Uncomment this to see stdout/stderr from chromedriver.
// agouti.Debug,
)
require.NoError(t, agoutiDriver.Start())
t.Cleanup(func() { require.NoError(t, agoutiDriver.Stop()) })
page, err := agoutiDriver.NewPage(agouti.Browser("chrome"))
require.NoError(t, err)
require.NoError(t, page.Reset())
return page
}
// WaitForVisibleElements expects the page to contain all the the elements specified by the selectors. It waits for this
// to occur and times out, failing the test, if they never appear.
func WaitForVisibleElements(t *testing.T, page *agouti.Page, selectors ...string) {
t.Helper()
require.Eventuallyf(t,
func() bool {
for _, sel := range selectors {
vis, err := page.First(sel).Visible()
if !(err == nil && vis) {
return false
}
}
return true
},
operationTimeout,
operationPollingInterval,
"expected to have a page with selectors %v, but it never loaded",
selectors,
)
}
// WaitForURL expects the page to eventually navigate to a URL matching the specified pattern. It waits for this
// to occur and times out, failing the test, if it never does.
func WaitForURL(t *testing.T, page *agouti.Page, pat *regexp.Regexp) {
var lastURL string
require.Eventuallyf(t,
func() bool {
url, err := page.URL()
if err == nil && pat.MatchString(url) {
return true
}
if url != lastURL {
t.Logf("saw URL %s", url)
lastURL = url
}
return false
},
operationTimeout,
operationPollingInterval,
"expected to browse to %s, but never got there",
pat,
)
}
// LoginToUpstream expects the page to be redirected to one of several known upstream IDPs.
// It knows how to enter the test username/password and submit the upstream login form.
func LoginToUpstream(t *testing.T, page *agouti.Page, upstream library.TestOIDCUpstream) {
t.Helper()
type config struct {
Name string
IssuerPattern *regexp.Regexp
LoginPagePattern *regexp.Regexp
UsernameSelector string
PasswordSelector string
LoginButtonSelector string
}
// Lookup the provider by matching on the issuer URL.
var cfg *config
for _, p := range []*config{
{
Name: "Okta",
IssuerPattern: regexp.MustCompile(`\Ahttps://.+\.okta\.com/.+\z`),
LoginPagePattern: regexp.MustCompile(`\Ahttps://.+\.okta\.com/.+\z`),
UsernameSelector: "input#okta-signin-username",
PasswordSelector: "input#okta-signin-password",
LoginButtonSelector: "input#okta-signin-submit",
},
{
Name: "Dex",
IssuerPattern: regexp.MustCompile(`\Ahttps://dex\.tools\.svc\.cluster\.local/dex.*\z`),
LoginPagePattern: regexp.MustCompile(`\Ahttps://dex\.tools\.svc\.cluster\.local/dex/auth/local.+\z`),
UsernameSelector: "input#login",
PasswordSelector: "input#password",
LoginButtonSelector: "button#submit-login",
},
} {
if p.IssuerPattern.MatchString(upstream.Issuer) {
cfg = p
break
}
}
if cfg == nil {
require.Failf(t, "could not find login provider for issuer %q", upstream.Issuer)
return
}
// Expect to be redirected to the login page.
t.Logf("waiting for redirect to %s login page", cfg.Name)
WaitForURL(t, page, cfg.LoginPagePattern)
// Wait for the login page to be rendered.
WaitForVisibleElements(t, page, cfg.UsernameSelector, cfg.PasswordSelector, cfg.LoginButtonSelector)
// Fill in the username and password and click "submit".
t.Logf("logging into %s", cfg.Name)
require.NoError(t, page.First(cfg.UsernameSelector).Fill(upstream.Username))
require.NoError(t, page.First(cfg.PasswordSelector).Fill(upstream.Password))
require.NoError(t, page.First(cfg.LoginButtonSelector).Click())
}