From 4512eeca9ac2ac567594ca483ea42302e240cb53 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Tue, 1 Aug 2023 09:04:21 -0700 Subject: [PATCH] Replace agouti and chromedriver with chromedp across the whole project --- CONTRIBUTING.md | 5 +- go.mod | 8 +- go.sum | 40 +-- hack/prepare-for-integration-tests.sh | 25 -- test/integration/cli_test.go | 15 +- test/integration/e2e_test.go | 75 ++-- test/integration/formposthtml_test.go | 106 +++--- test/integration/supervisor_login_test.go | 40 +-- test/integration/supervisor_warnings_test.go | 12 +- test/testlib/browsertest/browsertest.go | 342 ++++++++++++++----- 10 files changed, 389 insertions(+), 279 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f791b4d2..4960d94f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,6 @@ go build -o pinniped ./cmd/pinniped 1. Install dependencies: - - [`chromedriver`](https://chromedriver.chromium.org/) (and [Chrome](https://www.google.com/chrome/)) - [`docker`](https://www.docker.com/) - `htpasswd` (installed by default on MacOS, usually found in `apache2-utils` package for linux) - [`kapp`](https://carvel.dev/#getting-started) @@ -122,11 +121,13 @@ go build -o pinniped ./cmd/pinniped - [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - [`ytt`](https://carvel.dev/#getting-started) - [`nmap`](https://nmap.org/download.html) + - [`openssl`](https://www.openssl.org) (installed by default on MacOS) + - [Chrome](https://www.google.com/chrome/) On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already): ```bash - brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl chromedriver nmap && brew cask install docker + brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl nmap && brew cask install docker ``` 1. Create a kind cluster, compile, create container images, and install Pinniped and supporting test dependencies using: diff --git a/go.mod b/go.mod index 98906328..335e41c7 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 + github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 + github.com/chromedp/chromedp v0.9.1 github.com/coreos/go-oidc/v3 v3.6.0 github.com/creack/pty v1.1.18 github.com/davecgh/go-spew v1.1.1 @@ -26,7 +28,6 @@ require ( github.com/ory/fosite v0.44.0 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/errors v0.9.1 - github.com/sclevine/agouti v3.0.0+incompatible github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -63,6 +64,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chromedp/sysutil v1.0.0 // indirect github.com/coreos/go-oidc v2.2.1+incompatible // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.4.0 // indirect @@ -80,6 +82,9 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -105,7 +110,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect github.com/ory/go-acc v0.2.8 // indirect github.com/ory/go-convenience v0.1.0 // indirect github.com/ory/viper v1.7.5 // indirect diff --git a/go.sum b/go.sum index d6d79b27..19fb13f9 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9 h1:wMSvdj3BswqfQOXp2R1bJOAE7xIQLt2dlMQDMf836VY= +github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA= +github.com/chromedp/chromedp v0.9.1/go.mod h1:DUgZWRvYoEfgi66CgZ/9Yv+psgi+Sksy5DTScENWjaQ= +github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -187,7 +193,12 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -307,7 +318,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= @@ -349,6 +359,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -380,19 +392,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/fosite v0.44.0 h1:Z3UjyO11/wlIoa3BotOqcTkfm7kUNA8F7dd8mOMfx0o= github.com/ory/fosite v0.44.0/go.mod h1:o/G4kAeNn65l6MCod2+KmFfU6JQBSojS7eXys6lKGzM= github.com/ory/go-acc v0.2.8 h1:rOHHAPQjf0u7eHFGWpiXK+gIu/e0GRSJNr9pDukdNC4= @@ -459,8 +464,6 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -638,7 +641,6 @@ golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -663,7 +665,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -714,7 +715,6 @@ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -726,10 +726,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -753,8 +751,8 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -848,7 +846,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -980,7 +977,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -992,8 +988,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/hack/prepare-for-integration-tests.sh b/hack/prepare-for-integration-tests.sh index bb0fa104..c76c959f 100755 --- a/hack/prepare-for-integration-tests.sh +++ b/hack/prepare-for-integration-tests.sh @@ -50,7 +50,6 @@ skip_build=no clean_kind=no api_group_suffix="pinniped.dev" # same default as in the values.yaml ytt file dockerfile_path="" -skip_chromedriver_check=no get_active_directory_vars="" # specify a filename for a script to get AD related env variables alternate_deploy="undefined" @@ -78,10 +77,6 @@ while (("$#")); do api_group_suffix=$1 shift ;; - --live-dangerously) - skip_chromedriver_check=yes - shift - ;; --get-active-directory-vars) shift # If there are no more command line arguments, or there is another command line argument but it starts with a dash, then error @@ -153,28 +148,8 @@ check_dependency kapp "Please install kapp. e.g. 'brew tap vmware-tanzu/carvel & check_dependency kubectl "Please install kubectl. e.g. 'brew install kubectl' for MacOS" check_dependency htpasswd "Please install htpasswd. Should be pre-installed on MacOS. Usually found in 'apache2-utils' package for linux." check_dependency openssl "Please install openssl. Should be pre-installed on MacOS." -check_dependency chromedriver "Please install chromedriver. e.g. 'brew install chromedriver' for MacOS" check_dependency nmap "Please install nmap. e.g. 'brew install nmap' for MacOS" -# Check that Chrome and chromedriver versions match. If chromedriver falls a couple versions behind -# then usually tests start to fail with strange error messages. -if [[ "$skip_chromedriver_check" == "no" ]]; then - if [[ "$OSTYPE" == "darwin"* ]]; then - chrome_version=$(/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version | cut -d ' ' -f3 | cut -d '.' -f1) - else - chrome_version=$(google-chrome --version | cut -d ' ' -f3 | cut -d '.' -f1) - fi - chromedriver_version=$(chromedriver --version | cut -d ' ' -f2 | cut -d '.' -f1) - if [[ "$chrome_version" != "$chromedriver_version" ]]; then - log_error "It appears that you are using Chrome $chrome_version with chromedriver $chromedriver_version." - log_error "Please use the same version of chromedriver as Chrome." - log_error "If you are using the latest version of Chrome, then you can upgrade" - log_error "to the latest chromedriver, e.g. 'brew upgrade chromedriver' on MacOS." - log_error "Feeling lucky? Add --live-dangerously to skip this check." - exit 1 - fi -fi - # Require kubectl >= 1.18.x. if [ "$(kubectl version --client=true -o=json | grep gitVersion | cut -d '.' -f 2)" -lt 18 ]; then log_error "kubectl >= 1.18.x is required, you have $(kubectl version --client=true --short | cut -d ':' -f2)" diff --git a/test/integration/cli_test.go b/test/integration/cli_test.go index 1a2554d1..8f0cadc2 100644 --- a/test/integration/cli_test.go +++ b/test/integration/cli_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -280,7 +280,7 @@ func runPinnipedLoginOIDC( sessionCachePath := testutil.TempDir(t) + "/sessions.yaml" // Start the browser driver. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) // Start the CLI running the "login oidc [...]" command with stdout/stderr connected to pipes. cmd := oidcLoginCommand(ctx, t, pinnipedExe, sessionCachePath) @@ -334,22 +334,21 @@ func runPinnipedLoginOIDC( case loginURL = <-loginURLChan: } t.Logf("navigating to login page") - require.NoError(t, page.Navigate(loginURL)) + browser.Navigate(t, loginURL) // Expect to be redirected to the upstream provider and log in. - browsertest.LoginToUpstreamOIDC(t, page, env.CLIUpstreamOIDC) + browsertest.LoginToUpstreamOIDC(t, browser, env.CLIUpstreamOIDC) // Expect to be redirected to the localhost callback. t.Logf("waiting for redirect to callback") callbackURLPattern := regexp.MustCompile(`\A` + regexp.QuoteMeta(env.CLIUpstreamOIDC.CallbackURL) + `(\?.+)?\z`) - browsertest.WaitForURL(t, page, callbackURLPattern) + browser.WaitForURL(t, callbackURLPattern) // Wait for the "pre" element that gets rendered for a `text/plain` page, and // assert that it contains the success message. t.Logf("verifying success page") - browsertest.WaitForVisibleElements(t, page, "pre") - msg, err := page.First("pre").Text() - require.NoError(t, err) + browser.WaitForVisibleElements(t, "pre") + msg := browser.TextOfFirstMatch(t, "pre") require.Equal(t, "you have been logged in and may now close this tab", msg) // Expect the CLI to output an ExecCredential in JSON format. diff --git a/test/integration/e2e_test.go b/test/integration/e2e_test.go index 8e517675..bb872098 100644 --- a/test/integration/e2e_test.go +++ b/test/integration/e2e_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -24,7 +24,6 @@ import ( "time" "github.com/creack/pty" - "github.com/sclevine/agouti" "github.com/stretchr/testify/require" authorizationv1 "k8s.io/api/authorization/v1" corev1 "k8s.io/api/core/v1" @@ -123,7 +122,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamOIDC.Username expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups @@ -177,18 +176,18 @@ func TestE2EFullIntegration_Browser(t *testing.T) { kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...) // Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser. - kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page) + kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser) // Confirm that we got to the upstream IDP's login page, fill out the form, and submit the form. - browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC) + browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC) // Expect to be redirected to the downstream callback which is serving the form_post HTML. t.Logf("waiting for response page %s", downstream.Spec.Issuer) - browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) + browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) // The response page should have done the background fetch() and POST'ed to the CLI's callback. // It should now be in the "success" state. - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan)) @@ -204,7 +203,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamOIDC.Username expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups @@ -258,18 +257,18 @@ func TestE2EFullIntegration_Browser(t *testing.T) { kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...) // Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser. - kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page) + kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser) // Confirm that we got to the upstream IDP's login page, fill out the form, and submit the form. - browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC) + browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC) // Expect to be redirected to the downstream callback which is serving the form_post HTML. t.Logf("waiting for response page %s", downstream.Spec.Issuer) - browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) + browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) // The response page should have done the background fetch() and POST'ed to the CLI's callback. // It should now be in the "success" state. - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan)) @@ -288,7 +287,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamOIDC.Username expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups @@ -363,20 +362,20 @@ func TestE2EFullIntegration_Browser(t *testing.T) { require.NotEmptyf(t, loginURL, "didn't find login URL in output: %s", output) t.Logf("navigating to login page") - require.NoError(t, page.Navigate(loginURL)) + browser.Navigate(t, loginURL) // Expect to be redirected to the upstream provider and log in. - browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC) + browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC) // Expect to be redirected to the downstream callback which is serving the form_post HTML. t.Logf("waiting for response page %s", downstream.Spec.Issuer) - browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) + browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) // The response page should have failed to automatically post, and should now be showing the manual instructions. - authCode := formpostExpectManualState(t, page) + authCode := formpostExpectManualState(t, browser) // Enter the auth code in the waiting prompt, followed by a newline. - t.Logf("'manually' pasting authorization code %q to waiting prompt", authCode) + t.Logf("'manually' pasting authorization code with length %d to waiting prompt", len(authCode)) _, err = ptyFile.WriteString(authCode + "\n") require.NoError(t, err) @@ -399,7 +398,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamOIDC.Username expectedGroups := env.SupervisorUpstreamOIDC.ExpectedGroups @@ -488,20 +487,20 @@ func TestE2EFullIntegration_Browser(t *testing.T) { require.NotEmptyf(t, loginURL, "didn't find login URL in output: %s", output) t.Logf("navigating to login page") - require.NoError(t, page.Navigate(loginURL)) + browser.Navigate(t, loginURL) // Expect to be redirected to the upstream provider and log in. - browsertest.LoginToUpstreamOIDC(t, page, env.SupervisorUpstreamOIDC) + browsertest.LoginToUpstreamOIDC(t, browser, env.SupervisorUpstreamOIDC) // Expect to be redirected to the downstream callback which is serving the form_post HTML. t.Logf("waiting for response page %s", downstream.Spec.Issuer) - browsertest.WaitForURL(t, page, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) + browser.WaitForURL(t, regexp.MustCompile(regexp.QuoteMeta(downstream.Spec.Issuer))) // The response page should have failed to automatically post, and should now be showing the manual instructions. - authCode := formpostExpectManualState(t, page) + authCode := formpostExpectManualState(t, browser) // Enter the auth code in the waiting prompt, followed by a newline. - t.Logf("'manually' pasting authorization code %q to waiting prompt", authCode) + t.Logf("'manually' pasting authorization code with length %d to waiting prompt", len(authCode)) _, err = ptyFile.WriteString(authCode + "\n") require.NoError(t, err) @@ -1002,7 +1001,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs @@ -1029,13 +1028,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) { kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...) // Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser. - kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page) + kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser) // Confirm that we got to the Supervisor's login page, fill out the form, and submit the form. - browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer, + browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer, expectedUsername, env.SupervisorUpstreamLDAP.TestUserPassword) - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan)) @@ -1052,7 +1051,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue expectedGroups := env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames @@ -1079,13 +1078,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) { kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...) // Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser. - kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page) + kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser) // Confirm that we got to the Supervisor's login page, fill out the form, and submit the form. - browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer, + browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer, expectedUsername, env.SupervisorUpstreamActiveDirectory.TestUserPassword) - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan)) @@ -1102,7 +1101,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { tempDir := testutil.TempDir(t) // per-test tmp dir to avoid sharing files between tests // Start a fresh browser driver because we don't want to share cookies between the various tests in this file. - page := browsertest.Open(t) + browser := browsertest.OpenBrowser(t) expectedUsername := env.SupervisorUpstreamLDAP.TestUserMailAttributeValue expectedGroups := env.SupervisorUpstreamLDAP.TestUserDirectGroupsDNs @@ -1135,13 +1134,13 @@ func TestE2EFullIntegration_Browser(t *testing.T) { kubectlCmd.Env = append(os.Environ(), env.ProxyEnv()...) // Run the kubectl command, wait for the Pinniped CLI to print the authorization URL, and open it in the browser. - kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, page) + kubectlOutputChan := startKubectlAndOpenAuthorizationURLInBrowser(testCtx, t, kubectlCmd, browser) // Confirm that we got to the Supervisor's login page, fill out the form, and submit the form. - browsertest.LoginToUpstreamLDAP(t, page, downstream.Spec.Issuer, + browsertest.LoginToUpstreamLDAP(t, browser, downstream.Spec.Issuer, expectedUsername, env.SupervisorUpstreamLDAP.TestUserPassword) - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) requireKubectlGetNamespaceOutput(t, env, waitForKubectlOutput(t, kubectlOutputChan)) @@ -1149,7 +1148,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) { }) } -func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *testing.T, kubectlCmd *exec.Cmd, page *agouti.Page) chan string { +func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *testing.T, kubectlCmd *exec.Cmd, b *browsertest.Browser) chan string { // Wrap the stdout and stderr pipes with TeeReaders which will copy each incremental read to an // in-memory buffer, so we can have the full output available to us at the end. originalStderrPipe, err := kubectlCmd.StderrPipe() @@ -1226,7 +1225,7 @@ func startKubectlAndOpenAuthorizationURLInBrowser(testCtx context.Context, t *te case loginURL = <-loginURLChan: } t.Logf("navigating to login page: %q", loginURL) - require.NoError(t, page.Navigate(loginURL)) + b.Navigate(t, loginURL) return kubectlOutputChan } diff --git a/test/integration/formposthtml_test.go b/test/integration/formposthtml_test.go index 1fab4dcc..1a3b4529 100644 --- a/test/integration/formposthtml_test.go +++ b/test/integration/formposthtml_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -16,7 +16,6 @@ import ( "github.com/ory/fosite" "github.com/ory/fosite/token/hmac" - "github.com/sclevine/agouti" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,24 +32,25 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) { // Run a mock callback handler, simulating the one running in the CLI. callbackURL, expectCallback := formpostCallbackServer(t) - // Open a single browser for all subtests to use (in sequence). - page := browsertest.Open(t) - t.Run("success", func(t *testing.T) { + browser := browsertest.OpenBrowser(t) + // Serve the form_post template with successful parameters. responseParams := formpostRandomParams(t) - formpostInitiate(t, page, formpostTemplateServer(t, callbackURL, responseParams)) + formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL, responseParams)) // Now we handle the callback and assert that we got what we expected. This should transition // the UI into the success state. expectCallback(t, responseParams) - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) }) t.Run("callback server error", func(t *testing.T) { + browser := browsertest.OpenBrowser(t) + // Serve the form_post template with a redirect URI that will return an HTTP 500 response. responseParams := formpostRandomParams(t) - formpostInitiate(t, page, formpostTemplateServer(t, callbackURL+"?fail=500", responseParams)) + formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL+"?fail=500", responseParams)) // Now we handle the callback and assert that we got what we expected. expectCallback(t, responseParams) @@ -66,13 +66,15 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) { // In the future, we could change the Javascript code to use mode 'cors' // because we have upgraded our CLI callback endpoint to handle CORS, // and then we could change this to formpostExpectManualState(). - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) }) t.Run("network failure", func(t *testing.T) { + browser := browsertest.OpenBrowser(t) + // Serve the form_post template with a redirect URI that will return a network error. responseParams := formpostRandomParams(t) - formpostInitiate(t, page, formpostTemplateServer(t, callbackURL+"?fail=close", responseParams)) + formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL+"?fail=close", responseParams)) // Now we handle the callback and assert that we got what we expected. // This will trigger the callback server to close the client connection abruptly because @@ -80,28 +82,30 @@ func TestFormPostHTML_Browser_Parallel(t *testing.T) { expectCallback(t, responseParams) // This failure should cause the UI to enter the "manual" state. - actualCode := formpostExpectManualState(t, page) + actualCode := formpostExpectManualState(t, browser) require.Equal(t, responseParams.Get("code"), actualCode) }) t.Run("timeout", func(t *testing.T) { + browser := browsertest.OpenBrowser(t) + // Serve the form_post template with successful parameters. responseParams := formpostRandomParams(t) - formpostInitiate(t, page, formpostTemplateServer(t, callbackURL, responseParams)) + formpostInitiate(t, browser, formpostTemplateServer(t, callbackURL, responseParams)) // Sleep for longer than the two second timeout. // During this sleep we are blocking the callback from returning. time.Sleep(3 * time.Second) // Assert that the timeout fires and we see the manual instructions. - actualCode := formpostExpectManualState(t, page) + actualCode := formpostExpectManualState(t, browser) require.Equal(t, responseParams.Get("code"), actualCode) // Now simulate the callback finally succeeding, in which case // the manual instructions should disappear and we should see the success // div instead. expectCallback(t, responseParams) - formpostExpectSuccessState(t, page) + formpostExpectSuccessState(t, browser) }) } @@ -228,88 +232,66 @@ func formpostRandomParams(t *testing.T) url.Values { } } -// formpostExpectTitle asserts that the page has the expected title. -func formpostExpectTitle(t *testing.T, page *agouti.Page, expected string) { +// formpostExpectFavicon asserts that the page has the expected SVG/emoji favicon. +func formpostExpectFavicon(t *testing.T, b *browsertest.Browser, expected string) { t.Helper() - actual, err := page.Title() - require.NoError(t, err) - require.Equal(t, expected, actual) -} - -// formpostExpectTitle asserts that the page has the expected SVG/emoji favicon. -func formpostExpectFavicon(t *testing.T, page *agouti.Page, expected string) { - t.Helper() - iconURL, err := page.First("#favicon").Attribute("href") - require.NoError(t, err) + iconURL := b.AttrValueOfFirstMatch(t, "#favicon", "href") require.True(t, strings.HasPrefix(iconURL, "data:image/svg+xml, 0 { + t.Logf("Printing %d browser console events at end of test...", consoleEventCount) + } + for _, e := range b.consoleEvents { + args := make([]string, len(e.args)) + for i, arg := range e.args { + args[i] = fmt.Sprintf("%q", testlib.MaskTokens(arg)) + } + t.Logf("console.%s with args: [%s]", e.api, strings.Join(args, ", ")) + } + + if exceptionEventCount > 0 { + t.Logf("Printing %d browser exception events at end of test...", exceptionEventCount) + } + for _, e := range b.exceptionEvents { + t.Logf("exception: %s", e) + } + }) + + // Done. The browser is ready to be driven by the test. + return b +} + +func (b *Browser) timeout() time.Duration { + return 30 * time.Second +} + +func (b *Browser) runWithTimeout(t *testing.T, timeout time.Duration, actions ...chromedp.Action) { + t.Helper() + timeoutCtx, cancel := context.WithTimeout(b.chromeCtx, timeout) + t.Cleanup(cancel) + + err := chromedp.Run(timeoutCtx, actions...) + if err != nil && err == context.Canceled || err == context.DeadlineExceeded { + require.NoError(t, err, "the browser operation took longer than the allowed timeout") + } + require.NoError(t, err, "the browser operation failed") +} + +func (b *Browser) Navigate(t *testing.T, url string) { + t.Helper() + b.runWithTimeout(t, b.timeout(), chromedp.Navigate(url)) +} + +func (b *Browser) Title(t *testing.T) string { + t.Helper() + var title string + b.runWithTimeout(t, b.timeout(), chromedp.Title(&title)) + return title +} + +func (b *Browser) WaitForVisibleElements(t *testing.T, selectors ...string) { + t.Helper() + for _, s := range selectors { + b.runWithTimeout(t, b.timeout(), chromedp.WaitVisible(s)) + } +} + +func (b *Browser) TextOfFirstMatch(t *testing.T, selector string) string { + t.Helper() + var text string + b.runWithTimeout(t, b.timeout(), chromedp.Text(selector, &text, chromedp.NodeVisible)) + return text +} + +func (b *Browser) AttrValueOfFirstMatch(t *testing.T, selector string, attributeName string) string { + t.Helper() + var value string + var ok bool + b.runWithTimeout(t, b.timeout(), chromedp.AttributeValue(selector, attributeName, &value, &ok)) + require.Truef(t, ok, "did not find attribute named %q on first element returned by selector %q", attributeName, selector) + return value +} + +func (b *Browser) SendKeysToFirstMatch(t *testing.T, selector string, runesToType string) { + t.Helper() + b.runWithTimeout(t, b.timeout(), chromedp.SendKeys(selector, runesToType, chromedp.NodeVisible)) +} + +func (b *Browser) ClickFirstMatch(t *testing.T, selector string) string { + t.Helper() + var text string + b.runWithTimeout(t, b.timeout(), chromedp.Click(selector, chromedp.NodeVisible)) + return text +} + +// 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 (b *Browser) WaitForURL(t *testing.T, regex *regexp.Regexp) { + var lastURL string + testlib.RequireEventuallyf(t, + func(requireEventually *require.Assertions) { + var url string + requireEventually.NoError(chromedp.Run(b.chromeCtx, chromedp.Location(&url))) + if url != lastURL { + t.Logf("saw URL %s", testlib.MaskTokens(url)) + lastURL = url + } + requireEventually.Regexp(regex, url) + }, + 30*time.Second, + 100*time.Millisecond, + "expected to browse to %s, but never got there", + regex, + ) +} + +// FindConsoleEventWithTextMatching searches the browser's console that have been observed so far +// to find an event with an argument (converted to a string) that matches the provided regexp. +// consoleEventAPIType could be any of the console.funcName() names, e.g. "log", "info", "error", etc. +// It returns the first matching event argument value. It doesn't worry about optimizing the search +// speed because there should not be too many console events and because this just is a test helper. +func (b *Browser) FindConsoleEventWithTextMatching(consoleEventAPIType string, re *regexp.Regexp) (string, bool) { + b.lock.RLock() + defer b.lock.RUnlock() + + for _, e := range b.consoleEvents { + if e.api == consoleEventAPIType { + for _, arg := range e.args { + if re.Match([]byte(arg)) { + return arg, true + } + } + } + } + + return "", false } func rootTestName(t *testing.T) string { @@ -84,50 +281,9 @@ func rootTestName(t *testing.T) string { } } -// 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() - - testlib.RequireEventuallyf(t, - func(requireEventually *require.Assertions) { - for _, sel := range selectors { - vis, err := page.First(sel).Visible() - requireEventually.NoError(err) - requireEventually.Truef(vis, "expected element %q to be visible", sel) - } - }, - 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 - testlib.RequireEventuallyf(t, - func(requireEventually *require.Assertions) { - url, err := page.URL() - if url != lastURL { - t.Logf("saw URL %s", testlib.MaskTokens(url)) - lastURL = url - } - requireEventually.NoError(err) - requireEventually.Regexp(pat, url) - }, - operationTimeout, - operationPollingInterval, - "expected to browse to %s, but never got there", - pat, - ) -} - // LoginToUpstreamOIDC 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 LoginToUpstreamOIDC(t *testing.T, page *agouti.Page, upstream testlib.TestOIDCUpstream) { +func LoginToUpstreamOIDC(t *testing.T, b *Browser, upstream testlib.TestOIDCUpstream) { t.Helper() type config struct { @@ -171,21 +327,21 @@ func LoginToUpstreamOIDC(t *testing.T, page *agouti.Page, upstream testlib.TestO // Expect to be redirected to the login page. t.Logf("waiting for redirect to %s login page", cfg.Name) - WaitForURL(t, page, cfg.LoginPagePattern) + b.WaitForURL(t, cfg.LoginPagePattern) // Wait for the login page to be rendered. - WaitForVisibleElements(t, page, cfg.UsernameSelector, cfg.PasswordSelector, cfg.LoginButtonSelector) + b.WaitForVisibleElements(t, 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()) + b.SendKeysToFirstMatch(t, cfg.UsernameSelector, upstream.Username) + b.SendKeysToFirstMatch(t, cfg.PasswordSelector, upstream.Password) + b.ClickFirstMatch(t, cfg.LoginButtonSelector) } // LoginToUpstreamLDAP expects the page to be redirected to the Supervisor's login UI for an LDAP/AD IDP. // It knows how to enter the test username/password and submit the upstream login form. -func LoginToUpstreamLDAP(t *testing.T, page *agouti.Page, issuer, username, password string) { +func LoginToUpstreamLDAP(t *testing.T, b *Browser, issuer, username, password string) { t.Helper() loginURLRegexp, err := regexp.Compile(`\A` + regexp.QuoteMeta(issuer+"/login") + `\?state=.+\z`) @@ -193,34 +349,34 @@ func LoginToUpstreamLDAP(t *testing.T, page *agouti.Page, issuer, username, pass // Expect to be redirected to the login page. t.Logf("waiting for redirect to %s/login page", issuer) - WaitForURL(t, page, loginURLRegexp) + b.WaitForURL(t, loginURLRegexp) // Wait for the login page to be rendered. - WaitForVisibleElements(t, page, "#username", "#password", "#submit") + b.WaitForVisibleElements(t, "#username", "#password", "#submit") // Fill in the username and password and click "submit". - SubmitUpstreamLDAPLoginForm(t, page, username, password) + SubmitUpstreamLDAPLoginForm(t, b, username, password) } -func SubmitUpstreamLDAPLoginForm(t *testing.T, page *agouti.Page, username string, password string) { +func SubmitUpstreamLDAPLoginForm(t *testing.T, b *Browser, username string, password string) { t.Helper() // Fill in the username and password and click "submit". t.Logf("logging in via Supervisor's upstream LDAP/AD login UI page") - require.NoError(t, page.First("#username").Fill(username)) - require.NoError(t, page.First("#password").Fill(password)) - require.NoError(t, page.First("#submit").Click()) + b.SendKeysToFirstMatch(t, "#username", username) + b.SendKeysToFirstMatch(t, "#password", password) + b.ClickFirstMatch(t, "#submit") } -func WaitForUpstreamLDAPLoginPageWithError(t *testing.T, page *agouti.Page, issuer string) { +func WaitForUpstreamLDAPLoginPageWithError(t *testing.T, b *Browser, issuer string) { t.Helper() // Wait for redirect back to the login page again with an error. t.Logf("waiting for redirect to back to login page with error message") loginURLRegexp, err := regexp.Compile(`\A` + regexp.QuoteMeta(issuer+"/login") + `\?err=login_error&state=.+\z`) require.NoError(t, err) - WaitForURL(t, page, loginURLRegexp) + b.WaitForURL(t, loginURLRegexp) // Wait for the login page to be rendered again, this time also with an error message. - WaitForVisibleElements(t, page, "#username", "#password", "#submit", "#alert") + b.WaitForVisibleElements(t, "#username", "#password", "#submit", "#alert") }