diff --git a/go.mod b/go.mod index 007402ed..19eb4b05 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/fasthttp/router v1.4.4 github.com/fasthttp/session/v2 v2.4.4 github.com/go-ldap/ldap/v3 v3.4.1 + github.com/go-rod/rod v0.101.8 github.com/go-sql-driver/mysql v1.6.0 github.com/golang-jwt/jwt/v4 v4.1.0 github.com/golang/mock v1.6.0 @@ -30,7 +31,6 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 - github.com/tebeka/selenium v0.9.9 github.com/tstranex/u2f v1.0.0 github.com/valyala/fasthttp v1.31.0 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect diff --git a/go.sum b/go.sum index a5009bb9..86a0765c 100644 --- a/go.sum +++ b/go.sum @@ -43,10 +43,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e h1:4ZrkT/RzpnROylmoQL57iVUL57wGKTR5O6KpVnbm2tA= -github.com/BurntSushi/xgbutil v0.0.0-20160919175755-f7c97cef3b4e/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -89,8 +86,6 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -119,8 +114,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= @@ -315,6 +308,8 @@ github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7 github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-rod/rod v0.101.8 h1:oV0O97uwjkCVyAP0hD6K6bBE8FUMIjs0dtF7l6kEBsU= +github.com/go-rod/rod v0.101.8/go.mod h1:N/zlT53CfSpq74nb6rOR0K8UF0SPUPBmzBnArrms+mY= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -646,10 +641,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -1297,8 +1290,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tebeka/selenium v0.9.9 h1:cNziB+etNgyH/7KlNI7RMC1ua5aH1+5wUlFQyzeMh+w= -github.com/tebeka/selenium v0.9.9/go.mod h1:5Fr8+pUvU6B1OiPfkdCKdXZyr5znvVkxuPd0NOdZCQc= github.com/tidwall/gjson v1.11.0 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4= github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1342,6 +1333,16 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= +github.com/ysmood/goob v0.3.0 h1:XZ51cZJ4W3WCoCiUktixzMIQF86W7G5VFL4QQ/Q2uS0= +github.com/ysmood/goob v0.3.0/go.mod h1:S3lq113Y91y1UBf1wj1pFOxeahvfKkCk6mTWTWbDdWs= +github.com/ysmood/got v0.15.1 h1:X5jAbMyBf5yeezuFMp9HaMGXZWMSqIQcUlAHI+kJmUs= +github.com/ysmood/got v0.15.1/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= +github.com/ysmood/gotrace v0.2.2 h1:006KHGRThSRf8lwh4EyhNmuuq/l+Ygs+JqojkhEG1/E= +github.com/ysmood/gotrace v0.2.2/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= +github.com/ysmood/gson v0.6.4 h1:Yb6tosv6bk59HqjZu2/7o4BFherpYEMkDkXmlhgryZ4= +github.com/ysmood/gson v0.6.4/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw= +github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/suites/action_2fa_methods.go b/internal/suites/action_2fa_methods.go index 55d90fa6..96f35e7c 100644 --- a/internal/suites/action_2fa_methods.go +++ b/internal/suites/action_2fa_methods.go @@ -1,17 +1,17 @@ package suites import ( - "context" "fmt" "testing" + "github.com/go-rod/rod" "github.com/stretchr/testify/require" ) -func (wds *WebDriverSession) doChangeMethod(ctx context.Context, t *testing.T, method string) { - err := wds.WaitElementLocatedByID(ctx, t, "methods-button").Click() +func (rs *RodSession) doChangeMethod(t *testing.T, page *rod.Page, method string) { + err := rs.WaitElementLocatedByCSSSelector(t, page, "methods-button").Click("left") require.NoError(t, err) - wds.WaitElementLocatedByID(ctx, t, "methods-dialog") - err = wds.WaitElementLocatedByID(ctx, t, fmt.Sprintf("%s-option", method)).Click() + rs.WaitElementLocatedByCSSSelector(t, page, "methods-dialog") + err = rs.WaitElementLocatedByCSSSelector(t, page, fmt.Sprintf("%s-option", method)).Click("left") require.NoError(t, err) } diff --git a/internal/suites/action_login.go b/internal/suites/action_login.go index 64ead3a4..fcb281c1 100644 --- a/internal/suites/action_login.go +++ b/internal/suites/action_login.go @@ -1,44 +1,44 @@ package suites import ( - "context" "testing" "time" + "github.com/go-rod/rod" "github.com/stretchr/testify/require" ) -func (wds *WebDriverSession) doFillLoginPageAndClick(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) { - usernameElement := wds.WaitElementLocatedByID(ctx, t, "username-textfield") - err := usernameElement.SendKeys(username) +func (rs *RodSession) doFillLoginPageAndClick(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool) { + usernameElement := rs.WaitElementLocatedByCSSSelector(t, page, "username-textfield") + err := usernameElement.Input(username) require.NoError(t, err) - passwordElement := wds.WaitElementLocatedByID(ctx, t, "password-textfield") - err = passwordElement.SendKeys(password) + passwordElement := rs.WaitElementLocatedByCSSSelector(t, page, "password-textfield") + err = passwordElement.Input(password) require.NoError(t, err) if keepMeLoggedIn { - keepMeLoggedInElement := wds.WaitElementLocatedByID(ctx, t, "remember-checkbox") - err = keepMeLoggedInElement.Click() + keepMeLoggedInElement := rs.WaitElementLocatedByCSSSelector(t, page, "remember-checkbox") + err = keepMeLoggedInElement.Click("left") require.NoError(t, err) } - buttonElement := wds.WaitElementLocatedByID(ctx, t, "sign-in-button") - err = buttonElement.Click() + buttonElement := rs.WaitElementLocatedByCSSSelector(t, page, "sign-in-button") + err = buttonElement.Click("left") require.NoError(t, err) } // Login 1FA. -func (wds *WebDriverSession) doLoginOneFactor(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) { - wds.doVisitLoginPage(ctx, t, targetURL) - wds.doFillLoginPageAndClick(ctx, t, username, password, keepMeLoggedIn) +func (rs *RodSession) doLoginOneFactor(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, targetURL string) { + rs.doVisitLoginPage(t, page, targetURL) + rs.doFillLoginPageAndClick(t, page, username, password, keepMeLoggedIn) } // Login 1FA and 2FA subsequently (must already be registered). -func (wds *WebDriverSession) doLoginTwoFactor(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, otpSecret, targetURL string) { - wds.doLoginOneFactor(ctx, t, username, password, keepMeLoggedIn, targetURL) - wds.verifyIsSecondFactorPage(ctx, t) - wds.doValidateTOTP(ctx, t, otpSecret) +func (rs *RodSession) doLoginTwoFactor(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, otpSecret, targetURL string) { + rs.doLoginOneFactor(t, page, username, password, keepMeLoggedIn, targetURL) + rs.verifyIsSecondFactorPage(t, page) + rs.doValidateTOTP(t, page, otpSecret) // timeout when targetURL is not defined to prevent a show stopping redirect when visiting a protected domain if targetURL == "" { time.Sleep(1 * time.Second) @@ -46,20 +46,20 @@ func (wds *WebDriverSession) doLoginTwoFactor(ctx context.Context, t *testing.T, } // Login 1FA and register 2FA. -func (wds *WebDriverSession) doLoginAndRegisterTOTP(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool) string { - wds.doLoginOneFactor(ctx, t, username, password, keepMeLoggedIn, "") - secret := wds.doRegisterTOTP(ctx, t) - wds.doVisit(t, GetLoginBaseURL()) - wds.verifyIsSecondFactorPage(ctx, t) +func (rs *RodSession) doLoginAndRegisterTOTP(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool) string { + rs.doLoginOneFactor(t, page, username, password, keepMeLoggedIn, "") + secret := rs.doRegisterTOTP(t, page) + rs.doVisit(t, page, GetLoginBaseURL()) + rs.verifyIsSecondFactorPage(t, page) return secret } // Register a user with TOTP, logout and then authenticate until TOTP-2FA. -func (wds *WebDriverSession) doRegisterAndLogin2FA(ctx context.Context, t *testing.T, username, password string, keepMeLoggedIn bool, targetURL string) string { //nolint:unparam +func (rs *RodSession) doRegisterAndLogin2FA(t *testing.T, page *rod.Page, username, password string, keepMeLoggedIn bool, targetURL string) string { //nolint:unparam // Register TOTP secret and logout. - secret := wds.doRegisterThenLogout(ctx, t, username, password) - wds.doLoginTwoFactor(ctx, t, username, password, keepMeLoggedIn, secret, targetURL) + secret := rs.doRegisterThenLogout(t, page, username, password) + rs.doLoginTwoFactor(t, page, username, password, keepMeLoggedIn, secret, targetURL) return secret } diff --git a/internal/suites/action_logout.go b/internal/suites/action_logout.go index 36cac95c..c8c27afc 100644 --- a/internal/suites/action_logout.go +++ b/internal/suites/action_logout.go @@ -1,25 +1,28 @@ package suites import ( - "context" "fmt" "net/url" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) doLogout(ctx context.Context, t *testing.T) { - wds.doVisit(t, fmt.Sprintf("%s%s", GetLoginBaseURL(), "/logout")) - wds.verifyIsFirstFactorPage(ctx, t) +func (rs *RodSession) doLogout(t *testing.T, page *rod.Page) { + rs.doVisit(t, page, fmt.Sprintf("%s%s", GetLoginBaseURL(), "/logout")) + rs.verifyIsFirstFactorPage(t, page) } -func (wds *WebDriverSession) doLogoutWithRedirect(ctx context.Context, t *testing.T, targetURL string, firstFactor bool) { - wds.doVisit(t, fmt.Sprintf("%s%s%s", GetLoginBaseURL(), "/logout?rd=", url.QueryEscape(targetURL))) +func (rs *RodSession) doLogoutWithRedirect(t *testing.T, page *rod.Page, targetURL string, firstFactor bool) { + rs.doVisit(t, page, fmt.Sprintf("%s%s%s", GetLoginBaseURL(), "/logout?rd=", url.QueryEscape(targetURL))) if firstFactor { - wds.verifyIsFirstFactorPage(ctx, t) + rs.verifyIsFirstFactorPage(t, page) return } - wds.verifyURLIs(ctx, t, targetURL) + page.MustElementR("h1", "Public resource") + + rs.verifyURLIs(t, page, targetURL) } diff --git a/internal/suites/action_register.go b/internal/suites/action_register.go index 728e721d..c8328b5f 100644 --- a/internal/suites/action_register.go +++ b/internal/suites/action_register.go @@ -1,13 +1,14 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) doRegisterThenLogout(ctx context.Context, t *testing.T, username, password string) string { - secret := wds.doLoginAndRegisterTOTP(ctx, t, username, password, false) - wds.doLogout(ctx, t) +func (rs *RodSession) doRegisterThenLogout(t *testing.T, page *rod.Page, username, password string) string { + secret := rs.doLoginAndRegisterTOTP(t, page, username, password, false) + rs.doLogout(t, page) return secret } diff --git a/internal/suites/action_reset_password.go b/internal/suites/action_reset_password.go index 69f566f9..d0fa3822 100644 --- a/internal/suites/action_reset_password.go +++ b/internal/suites/action_reset_password.go @@ -1,52 +1,60 @@ package suites import ( - "context" "testing" + "time" + "github.com/go-rod/rod" "github.com/stretchr/testify/require" ) -func (wds *WebDriverSession) doInitiatePasswordReset(ctx context.Context, t *testing.T, username string) { - err := wds.WaitElementLocatedByID(ctx, t, "reset-password-button").Click() +func (rs *RodSession) doInitiatePasswordReset(t *testing.T, page *rod.Page, username string) { + err := rs.WaitElementLocatedByCSSSelector(t, page, "reset-password-button").Click("left") require.NoError(t, err) // Fill in username - err = wds.WaitElementLocatedByID(ctx, t, "username-textfield").SendKeys(username) + err = rs.WaitElementLocatedByCSSSelector(t, page, "username-textfield").Input(username) require.NoError(t, err) // And click on the reset button - err = wds.WaitElementLocatedByID(ctx, t, "reset-button").Click() + err = rs.WaitElementLocatedByCSSSelector(t, page, "reset-button").Click("left") require.NoError(t, err) } -func (wds *WebDriverSession) doCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) { +func (rs *RodSession) doCompletePasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) { link := doGetLinkFromLastMail(t) - wds.doVisit(t, link) + rs.doVisit(t, page, link) - err := wds.WaitElementLocatedByID(ctx, t, "password1-textfield").SendKeys(newPassword1) + time.Sleep(1 * time.Second) + + err := rs.WaitElementLocatedByCSSSelector(t, page, "password1-textfield").Input(newPassword1) require.NoError(t, err) - err = wds.WaitElementLocatedByID(ctx, t, "password2-textfield").SendKeys(newPassword2) + + time.Sleep(1 * time.Second) + + err = rs.WaitElementLocatedByCSSSelector(t, page, "password2-textfield").Input(newPassword2) require.NoError(t, err) - err = wds.WaitElementLocatedByID(ctx, t, "reset-button").Click() + + err = rs.WaitElementLocatedByCSSSelector(t, page, "reset-button").Click("left") require.NoError(t, err) } -func (wds *WebDriverSession) doSuccessfullyCompletePasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) { - wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2) - wds.verifyIsFirstFactorPage(ctx, t) +func (rs *RodSession) doSuccessfullyCompletePasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) { + rs.doCompletePasswordReset(t, page, newPassword1, newPassword2) + rs.verifyIsFirstFactorPage(t, page) } -func (wds *WebDriverSession) doUnsuccessfulPasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) { - wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2) +func (rs *RodSession) doUnsuccessfulPasswordReset(t *testing.T, page *rod.Page, newPassword1, newPassword2 string) { + rs.doCompletePasswordReset(t, page, newPassword1, newPassword2) + rs.verifyNotificationDisplayed(t, page, "Your supplied password does not meet the password policy requirements.") } -func (wds *WebDriverSession) doResetPassword(ctx context.Context, t *testing.T, username, newPassword1, newPassword2 string, unsuccessful bool) { - wds.doInitiatePasswordReset(ctx, t, username) +func (rs *RodSession) doResetPassword(t *testing.T, page *rod.Page, username, newPassword1, newPassword2 string, unsuccessful bool) { + rs.doInitiatePasswordReset(t, page, username) // then wait for the "email sent notification" - wds.verifyMailNotificationDisplayed(ctx, t) + rs.verifyMailNotificationDisplayed(t, page) if unsuccessful { - wds.doUnsuccessfulPasswordReset(ctx, t, newPassword1, newPassword2) + rs.doUnsuccessfulPasswordReset(t, page, newPassword1, newPassword2) } else { - wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2) + rs.doSuccessfullyCompletePasswordReset(t, page, newPassword1, newPassword2) } } diff --git a/internal/suites/action_totp.go b/internal/suites/action_totp.go index 2461e6a3..95e1a72e 100644 --- a/internal/suites/action_totp.go +++ b/internal/suites/action_totp.go @@ -1,43 +1,42 @@ package suites import ( - "context" "strings" "testing" "time" + "github.com/go-rod/rod" "github.com/pquerna/otp/totp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func (wds *WebDriverSession) doRegisterTOTP(ctx context.Context, t *testing.T) string { - err := wds.WaitElementLocatedByID(ctx, t, "register-link").Click() +func (rs *RodSession) doRegisterTOTP(t *testing.T, page *rod.Page) string { + err := rs.WaitElementLocatedByCSSSelector(t, page, "register-link").Click("left") require.NoError(t, err) - wds.verifyMailNotificationDisplayed(ctx, t) + rs.verifyMailNotificationDisplayed(t, page) link := doGetLinkFromLastMail(t) - wds.doVisit(t, link) - secretURL, err := wds.WaitElementLocatedByID(ctx, t, "secret-url").GetAttribute("value") + rs.doVisit(t, page, link) + secretURL, err := page.MustElement("#secret-url").Attribute("value") assert.NoError(t, err) - secret := secretURL[strings.LastIndex(secretURL, "=")+1:] + secret := (*secretURL)[strings.LastIndex(*secretURL, "=")+1:] assert.NotEqual(t, "", secret) assert.NotNil(t, secret) return secret } -func (wds *WebDriverSession) doEnterOTP(ctx context.Context, t *testing.T, code string) { - inputs := wds.WaitElementsLocatedByCSSSelector(ctx, t, "#otp-input input") +func (rs *RodSession) doEnterOTP(t *testing.T, page *rod.Page, code string) { + inputs := rs.WaitElementsLocatedByCSSSelector(t, page, "otp-input input") for i := 0; i < 6; i++ { - err := inputs[i].SendKeys(string(code[i])) - require.NoError(t, err) + _ = inputs[i].Input(string(code[i])) } } -func (wds *WebDriverSession) doValidateTOTP(ctx context.Context, t *testing.T, secret string) { +func (rs *RodSession) doValidateTOTP(t *testing.T, page *rod.Page, secret string) { code, err := totp.GenerateCode(secret, time.Now()) assert.NoError(t, err) - wds.doEnterOTP(ctx, t, code) + rs.doEnterOTP(t, page, code) } diff --git a/internal/suites/action_visit.go b/internal/suites/action_visit.go index 62c47320..5fb36b70 100644 --- a/internal/suites/action_visit.go +++ b/internal/suites/action_visit.go @@ -1,28 +1,36 @@ package suites import ( - "context" "fmt" "testing" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/proto" "github.com/stretchr/testify/assert" ) -func (wds *WebDriverSession) doVisit(t *testing.T, url string) { - err := wds.WebDriver.Get(url) +func (rs *RodSession) doCreateTab(t *testing.T, url string) *rod.Page { + p, err := rs.WebDriver.MustIncognito().Page(proto.TargetCreateTarget{URL: url}) + assert.NoError(t, err) + + return p +} + +func (rs *RodSession) doVisit(t *testing.T, page *rod.Page, url string) { + err := page.Navigate(url) assert.NoError(t, err) } -func (wds *WebDriverSession) doVisitAndVerifyOneFactorStep(ctx context.Context, t *testing.T, url string) { - wds.doVisit(t, url) - wds.verifyIsFirstFactorPage(ctx, t) +func (rs *RodSession) doVisitAndVerifyOneFactorStep(t *testing.T, page *rod.Page, url string) { + rs.doVisit(t, page, url) + rs.verifyIsFirstFactorPage(t, page) } -func (wds *WebDriverSession) doVisitLoginPage(ctx context.Context, t *testing.T, targetURL string) { +func (rs *RodSession) doVisitLoginPage(t *testing.T, page *rod.Page, targetURL string) { suffix := "" if targetURL != "" { suffix = fmt.Sprintf("?rd=%s", targetURL) } - wds.doVisitAndVerifyOneFactorStep(ctx, t, fmt.Sprintf("%s/%s", GetLoginBaseURL(), suffix)) + rs.doVisitAndVerifyOneFactorStep(t, page, fmt.Sprintf("%s/%s", GetLoginBaseURL(), suffix)) } diff --git a/internal/suites/const.go b/internal/suites/const.go index d1300159..2a252cfd 100644 --- a/internal/suites/const.go +++ b/internal/suites/const.go @@ -51,7 +51,6 @@ var DuoBaseURL = "https://duo.example.com" var AutheliaBaseURL = "https://authelia.example.com:9091" const stringTrue = "true" -const defaultChromeDriverPort = "4444" const testUsername = "john" const testPassword = "password" diff --git a/internal/suites/example/compose/authelia/resources/reflex.conf b/internal/suites/example/compose/authelia/resources/reflex.conf index 7be5ae36..68cd2652 100644 --- a/internal/suites/example/compose/authelia/resources/reflex.conf +++ b/internal/suites/example/compose/authelia/resources/reflex.conf @@ -1 +1 @@ --R '^web/' -r '(\.go$|go\.mod|\.sh|\.yml)' -s /resources/run-backend-dev.sh \ No newline at end of file +-R '^web/' -R 'users.yml' -r '(\.go$|go\.mod|\.sh|\.yml)' -s /resources/run-backend-dev.sh \ No newline at end of file diff --git a/internal/suites/example/compose/haproxy/haproxy.cfg b/internal/suites/example/compose/haproxy/haproxy.cfg index bf682ba0..ed06a451 100644 --- a/internal/suites/example/compose/haproxy/haproxy.cfg +++ b/internal/suites/example/compose/haproxy/haproxy.cfg @@ -4,11 +4,15 @@ global log stdout format raw local0 debug defaults + default-server init-addr none mode http log global option httplog option forwardfor +resolvers docker + nameserver ip 127.0.0.11:53 + frontend fe_api bind *:8081 ssl crt /usr/local/etc/haproxy/haproxy.pem @@ -63,13 +67,13 @@ backend be_auth_request listen be_auth_request_proxy mode http bind 127.0.0.1:8085 - server authelia-backend authelia-backend:9091 ssl verify none + server authelia-backend authelia-backend:9091 resolvers docker ssl verify none backend be_authelia - server authelia-backend authelia-backend:9091 ssl verify none + server authelia-backend authelia-backend:9091 resolvers docker ssl verify none backend fe_authelia - server authelia-frontend authelia-frontend:3000 + server authelia-frontend authelia-frontend:3000 resolvers docker backend be_httpbin acl remote_user_exist var(req.auth_response_header.remote_user) -m found @@ -81,10 +85,10 @@ backend be_httpbin http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist - server httpbin-backend httpbin:8000 + server httpbin-backend httpbin:8000 resolvers docker backend be_mail - server smtp-backend smtp:1080 + server smtp-backend smtp:1080 resolvers docker backend be_protected - server nginx-backend nginx-backend:80 + server nginx-backend nginx-backend:80 resolvers docker diff --git a/internal/suites/scenario_available_methods_test.go b/internal/suites/scenario_available_methods_test.go index 995aa220..976fb70d 100644 --- a/internal/suites/scenario_available_methods_test.go +++ b/internal/suites/scenario_available_methods_test.go @@ -5,36 +5,34 @@ import ( "log" "time" - "github.com/tebeka/selenium" - "github.com/authelia/authelia/v4/internal/utils" ) type AvailableMethodsScenario struct { - *SeleniumSuite + *RodSuite methods []string } func NewAvailableMethodsScenario(methods []string) *AvailableMethodsScenario { return &AvailableMethodsScenario{ - SeleniumSuite: new(SeleniumSuite), - methods: methods, + RodSuite: new(RodSuite), + methods: methods, } } func (s *AvailableMethodsScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.SeleniumSuite.WebDriverSession = wds + s.RodSession = browser } func (s *AvailableMethodsScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -42,26 +40,30 @@ func (s *AvailableMethodsScenario) TearDownSuite() { } func (s *AvailableMethodsScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *AvailableMethodsScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") - methodsButton := s.WaitElementLocatedByID(ctx, s.T(), "methods-button") - err := methodsButton.Click() + methodsButton := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-button") + err := methodsButton.Click("left") s.Assert().NoError(err) - methodsDialog := s.WaitElementLocatedByID(ctx, s.T(), "methods-dialog") - options, err := methodsDialog.FindElements(selenium.ByClassName, "method-option") + methodsDialog := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "methods-dialog") + options, err := methodsDialog.Elements(".method-option") s.Assert().NoError(err) s.Assert().Len(options, len(s.methods)) diff --git a/internal/suites/scenario_bypass_policy_test.go b/internal/suites/scenario_bypass_policy_test.go index 85f3e041..497b837e 100644 --- a/internal/suites/scenario_bypass_policy_test.go +++ b/internal/suites/scenario_bypass_policy_test.go @@ -11,27 +11,27 @@ import ( ) type BypassPolicyScenario struct { - *SeleniumSuite + *RodSuite } func NewBypassPolicyScenario() *BypassPolicyScenario { return &BypassPolicyScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *BypassPolicyScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *BypassPolicyScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -39,23 +39,27 @@ func (s *BypassPolicyScenario) TearDownSuite() { } func (s *BypassPolicyScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *BypassPolicyScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *BypassPolicyScenario) TestShouldAccessPublicResource() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), AdminBaseURL) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), AdminBaseURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) - s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL)) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", PublicBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func TestBypassPolicyScenario(t *testing.T) { diff --git a/internal/suites/scenario_custom_headers_test.go b/internal/suites/scenario_custom_headers_test.go index ea5edcde..e03c903e 100644 --- a/internal/suites/scenario_custom_headers_test.go +++ b/internal/suites/scenario_custom_headers_test.go @@ -10,33 +10,31 @@ import ( "time" mapset "github.com/deckarep/golang-set" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/tebeka/selenium" ) type CustomHeadersScenario struct { - *SeleniumSuite + *RodSuite } func NewCustomHeadersScenario() *CustomHeadersScenario { return &CustomHeadersScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *CustomHeadersScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *CustomHeadersScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -44,23 +42,26 @@ func (s *CustomHeadersScenario) TearDownSuite() { } func (s *CustomHeadersScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *CustomHeadersScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticatedUser() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), fmt.Sprintf("%s/headers", PublicBaseURL)) + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/headers", PublicBaseURL)) - body, err := s.WebDriver().FindElement(selenium.ByTagName, "body") + body, err := s.Context(ctx).Element("body") s.Assert().NoError(err) - s.WaitElementTextContains(ctx, s.T(), body, "\"Host\"") b, err := body.Text() s.Assert().NoError(err) @@ -82,46 +83,46 @@ type HeadersPayload struct { } func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUser() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() expectedGroups := mapset.NewSetWith("dev", "admins") targetURL := fmt.Sprintf("%s/headers", PublicBaseURL) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL) - s.verifyURLIs(ctx, s.T(), targetURL) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifyIsPublic(s.T(), s.Context(ctx)) - err := s.Wait(ctx, func(d selenium.WebDriver) (bool, error) { - body, err := s.WebDriver().FindElement(selenium.ByTagName, "body") - if err != nil { - return false, err - } + body, err := s.Context(ctx).Element("body") + s.Assert().NoError(err) + s.Assert().NotNil(body) - if body == nil { - return false, nil - } + content, err := body.Text() + s.Assert().NoError(err) + s.Assert().NotNil(content) - content, err := body.Text() - if err != nil { - return false, err - } + payload := HeadersPayload{} + if err := json.Unmarshal([]byte(content), &payload); err != nil { + log.Panic(err) + } - payload := HeadersPayload{} - if err := json.Unmarshal([]byte(content), &payload); err != nil { - return false, err - } + groups := strings.Split(payload.Headers.ForwardedGroups, ",") + actualGroups := mapset.NewSet() - groups := strings.Split(payload.Headers.ForwardedGroups, ",") - actualGroups := mapset.NewSet() - for _, group := range groups { - actualGroups.Add(group) - } + for _, group := range groups { + actualGroups.Add(group) + } - return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) && - strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com"), nil - }) + if strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) && + strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com") { + err = nil + } else { + err = fmt.Errorf("headers do not include user information") + } - require.NoError(s.T(), err) + s.Require().NoError(err) } func TestCustomHeadersScenario(t *testing.T) { diff --git a/internal/suites/scenario_default_redirection_url_test.go b/internal/suites/scenario_default_redirection_url_test.go index b4dd7c65..6dd52130 100644 --- a/internal/suites/scenario_default_redirection_url_test.go +++ b/internal/suites/scenario_default_redirection_url_test.go @@ -11,57 +11,62 @@ import ( ) type DefaultRedirectionURLScenario struct { - *SeleniumSuite + *RodSuite secret string } func NewDefaultRedirectionURLScenario() *DefaultRedirectionURLScenario { return &DefaultRedirectionURLScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } -func (drus *DefaultRedirectionURLScenario) SetupSuite() { - wds, err := StartWebDriver() +func (s *DefaultRedirectionURLScenario) SetupSuite() { + browser, err := StartRod() if err != nil { log.Fatal(err) } - drus.WebDriverSession = wds + s.RodSession = browser +} +func (s *DefaultRedirectionURLScenario) TearDownSuite() { + err := s.RodSession.Stop() + + if err != nil { + log.Fatal(err) + } +} + +func (s *DefaultRedirectionURLScenario) SetupTest() { + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} + +func (s *DefaultRedirectionURLScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() +} + +func (s *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - drus.secret = drus.doRegisterAndLogin2FA(ctx, drus.T(), "john", "password", false, targetURL) - drus.verifySecretAuthorized(ctx, drus.T()) -} -func (drus *DefaultRedirectionURLScenario) TearDownSuite() { - err := drus.WebDriverSession.Stop() + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) + s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) + s.doLogout(s.T(), s.Context(ctx)) - if err != nil { - log.Fatal(err) - } -} - -func (drus *DefaultRedirectionURLScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - drus.doLogout(ctx, drus.T()) - drus.doVisit(drus.T(), HomeBaseURL) - drus.verifyIsHome(ctx, drus.T()) -} - -func (drus *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - drus.doLoginTwoFactor(ctx, drus.T(), "john", "password", false, drus.secret, "") - drus.verifyURLIs(ctx, drus.T(), HomeBaseURL+"/") + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "") + s.verifyIsHome(s.T(), s.Page) } func TestShouldRunDefaultRedirectionURLScenario(t *testing.T) { diff --git a/internal/suites/scenario_inactivity_test.go b/internal/suites/scenario_inactivity_test.go index 83cbdc14..778b183c 100644 --- a/internal/suites/scenario_inactivity_test.go +++ b/internal/suites/scenario_inactivity_test.go @@ -11,35 +11,42 @@ import ( ) type InactivityScenario struct { - *SeleniumSuite + *RodSuite secret string } func NewInactivityScenario() *InactivityScenario { return &InactivityScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *InactivityScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + s.collectCoverage(s.Page) + s.MustClose() + }() + + s.Page = s.doCreateTab(s.T(), HomeBaseURL) targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func (s *InactivityScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -47,71 +54,78 @@ func (s *InactivityScenario) TearDownSuite() { } func (s *InactivityScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *InactivityScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *InactivityScenario) TestShouldRequireReauthenticationAfterInactivityPeriod() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "") + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) time.Sleep(6 * time.Second) - s.doVisit(s.T(), targetURL) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), targetURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) } func (s *InactivityScenario) TestShouldRequireReauthenticationAfterCookieExpiration() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, s.secret, "") + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, s.secret, "") for i := 0; i < 3; i++ { - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) time.Sleep(2 * time.Second) - s.doVisit(s.T(), targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) - time.Sleep(2 * time.Second) - s.doVisit(s.T(), targetURL) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), targetURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) } func (s *InactivityScenario) TestShouldDisableCookieExpirationAndInactivity() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", true, s.secret, "") - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", true, s.secret, "") + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) time.Sleep(10 * time.Second) - s.doVisit(s.T(), targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func TestInactivityScenario(t *testing.T) { diff --git a/internal/suites/scenario_oidc_test.go b/internal/suites/scenario_oidc_test.go index d531a2da..f97ce0e1 100644 --- a/internal/suites/scenario_oidc_test.go +++ b/internal/suites/scenario_oidc_test.go @@ -12,33 +12,40 @@ import ( ) type OIDCScenario struct { - *SeleniumSuite + *RodSuite secret string } func NewOIDCScenario() *OIDCScenario { return &OIDCScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *OIDCScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) - s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, AdminBaseURL) + s.collectCoverage(s.Page) + s.MustClose() + }() + + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.secret = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, AdminBaseURL) } func (s *OIDCScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -47,65 +54,73 @@ func (s *OIDCScenario) TearDownSuite() { func (s *OIDCScenario) SetupTest() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL)) - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.Page = s.doCreateTab(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL)) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) +} + +func (s *OIDCScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *OIDCScenario) TestShouldAuthorizeAccessToOIDCApp() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), OIDCBaseURL) - s.verifyIsFirstFactorPage(ctx, s.T()) - s.doFillLoginPageAndClick(ctx, s.T(), "john", "password", false) - s.verifyIsSecondFactorPage(ctx, s.T()) - s.doValidateTOTP(ctx, s.T(), s.secret) - time.Sleep(2 * time.Second) + s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) + s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.doValidateTOTP(s.T(), s.Context(ctx), s.secret) - s.waitBodyContains(ctx, s.T(), "Not logged yet...") + s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...") - // this href represents the 'login' link - err := s.WaitElementLocatedByTagName(ctx, s.T(), "a").Click() + // Search for the 'login' link + err := s.Page.MustSearch("Log in").Click("left") assert.NoError(s.T(), err) - s.verifyIsConsentPage(ctx, s.T()) - - err = s.WaitElementLocatedByID(ctx, s.T(), "accept-button").Click() + s.verifyIsConsentPage(s.T(), s.Context(ctx)) + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "accept-button").Click("left") assert.NoError(s.T(), err) // Verify that the app is showing the info related to the user stored in the JWT token - time.Sleep(2 * time.Second) - s.waitBodyContains(ctx, s.T(), "Logged in as john!") + s.waitBodyContains(s.T(), s.Context(ctx), "Logged in as john!") } func (s *OIDCScenario) TestShouldDenyConsent() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), OIDCBaseURL) - s.verifyIsFirstFactorPage(ctx, s.T()) - s.doFillLoginPageAndClick(ctx, s.T(), "john", "password", false) - s.verifyIsSecondFactorPage(ctx, s.T()) - s.doValidateTOTP(ctx, s.T(), s.secret) - time.Sleep(1 * time.Second) + s.doVisit(s.T(), s.Context(ctx), OIDCBaseURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) + s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "password", false) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.doValidateTOTP(s.T(), s.Context(ctx), s.secret) - s.waitBodyContains(ctx, s.T(), "Not logged yet...") + s.waitBodyContains(s.T(), s.Context(ctx), "Not logged yet...") - // this href represents the 'login' link - err := s.WaitElementLocatedByTagName(ctx, s.T(), "a").Click() + // Search for the 'login' link + err := s.Page.MustSearch("Log in").Click("left") assert.NoError(s.T(), err) - s.verifyIsConsentPage(ctx, s.T()) + s.verifyIsConsentPage(s.T(), s.Context(ctx)) - err = s.WaitElementLocatedByID(ctx, s.T(), "deny-button").Click() + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "deny-button").Click("left") assert.NoError(s.T(), err) - time.Sleep(1 * time.Second) - s.verifyURLIs(ctx, s.T(), "https://oidc.example.com:8080/oauth2/callback?error=access_denied&error_description=User%20has%20rejected%20the%20scopes") + s.verifyIsOIDC(s.T(), s.Context(ctx), "oauth2:", "https://oidc.example.com:8080/oauth2/callback?error=access_denied&error_description=User%20has%20rejected%20the%20scopes") } func TestRunOIDCScenario(t *testing.T) { diff --git a/internal/suites/scenario_one_factor_test.go b/internal/suites/scenario_one_factor_test.go index febfae7b..312d6657 100644 --- a/internal/suites/scenario_one_factor_test.go +++ b/internal/suites/scenario_one_factor_test.go @@ -11,27 +11,27 @@ import ( ) type OneFactorSuite struct { - *SeleniumSuite + *RodSuite } func NewOneFactorScenario() *OneFactorSuite { return &OneFactorSuite{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *OneFactorSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *OneFactorSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -39,40 +39,50 @@ func (s *OneFactorSuite) TearDownSuite() { } func (s *OneFactorSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *OneFactorSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *OneFactorSuite) TestShouldAuthorizeSecretAfterOneFactor() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", SingleFactorBaseURL) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifySecretAuthorized(s.T(), s.Page) } func (s *OneFactorSuite) TestShouldRedirectToSecondFactor() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, targetURL) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) } func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL) - s.verifyIsFirstFactorPage(ctx, s.T()) - s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "bad-password", false, targetURL) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.") } func TestRunOneFactor(t *testing.T) { diff --git a/internal/suites/scenario_password_complexity_test.go b/internal/suites/scenario_password_complexity_test.go index 493f6293..78e44d0c 100644 --- a/internal/suites/scenario_password_complexity_test.go +++ b/internal/suites/scenario_password_complexity_test.go @@ -10,25 +10,25 @@ import ( ) type PasswordComplexityScenario struct { - *SeleniumSuite + *RodSuite } func NewPasswordComplexityScenario() *PasswordComplexityScenario { - return &PasswordComplexityScenario{SeleniumSuite: new(SeleniumSuite)} + return &PasswordComplexityScenario{RodSuite: new(RodSuite)} } func (s *PasswordComplexityScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *PasswordComplexityScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -36,24 +36,28 @@ func (s *PasswordComplexityScenario) TearDownSuite() { } func (s *PasswordComplexityScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *PasswordComplexityScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *PasswordComplexityScenario) TestShouldRejectPasswordReset() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) // Attempt to reset the password to a - s.doResetPassword(ctx, s.T(), "john", "a", "a", true) - s.verifyNotificationDisplayed(ctx, s.T(), "Your supplied password does not meet the password policy requirements.") + s.doResetPassword(s.T(), s.Context(ctx), "john", "a", "a", true) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Your supplied password does not meet the password policy requirements.") } func TestRunPasswordComplexityScenario(t *testing.T) { diff --git a/internal/suites/scenario_redirection_check_test.go b/internal/suites/scenario_redirection_check_test.go index af5f8b2e..4d0b7c4f 100644 --- a/internal/suites/scenario_redirection_check_test.go +++ b/internal/suites/scenario_redirection_check_test.go @@ -10,27 +10,27 @@ import ( ) type RedirectionCheckScenario struct { - *SeleniumSuite + *RodSuite } func NewRedirectionCheckScenario() *RedirectionCheckScenario { return &RedirectionCheckScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *RedirectionCheckScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *RedirectionCheckScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -38,12 +38,13 @@ func (s *RedirectionCheckScenario) TearDownSuite() { } func (s *RedirectionCheckScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *RedirectionCheckScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } var redirectionAuthorizations = map[string]bool{ @@ -59,21 +60,24 @@ var redirectionAuthorizations = map[string]bool{ func (s *RedirectionCheckScenario) TestShouldRedirectOnLoginOnlyWhenDomainIsSafe() { ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") for url, redirected := range redirectionAuthorizations { s.T().Run(url, func(t *testing.T) { - s.doLoginTwoFactor(ctx, t, "john", "password", false, secret, url) + s.doLoginTwoFactor(t, s.Context(ctx), "john", "password", false, secret, url) if redirected { - s.verifySecretAuthorized(ctx, t) + s.verifySecretAuthorized(t, s.Context(ctx)) } else { - s.verifyIsAuthenticatedPage(ctx, t) + s.verifyIsAuthenticatedPage(t, s.Context(ctx)) } - s.doLogout(ctx, t) + s.doLogout(t, s.Context(ctx)) }) } } @@ -91,11 +95,14 @@ var logoutRedirectionURLs = map[string]bool{ func (s *RedirectionCheckScenario) TestShouldRedirectOnLogoutOnlyWhenDomainIsSafe() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() for url, success := range logoutRedirectionURLs { s.T().Run(url, func(t *testing.T) { - s.doLogoutWithRedirect(ctx, t, url, !success) + s.doLogoutWithRedirect(t, s.Context(ctx), url, !success) }) } } diff --git a/internal/suites/scenario_redirection_url_test.go b/internal/suites/scenario_redirection_url_test.go index 5224faa2..6ec35c7d 100644 --- a/internal/suites/scenario_redirection_url_test.go +++ b/internal/suites/scenario_redirection_url_test.go @@ -11,50 +11,54 @@ import ( ) type RedirectionURLScenario struct { - *SeleniumSuite + *RodSuite } func NewRedirectionURLScenario() *RedirectionURLScenario { return &RedirectionURLScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } -func (rus *RedirectionURLScenario) SetupSuite() { - wds, err := StartWebDriver() +func (s *RedirectionURLScenario) SetupSuite() { + browser, err := StartRod() if err != nil { log.Fatal(err) } - rus.WebDriverSession = wds + s.RodSession = browser } -func (rus *RedirectionURLScenario) TearDownSuite() { - err := rus.WebDriverSession.Stop() +func (s *RedirectionURLScenario) TearDownSuite() { + err := s.RodSession.Stop() if err != nil { log.Fatal(err) } } -func (rus *RedirectionURLScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - rus.doLogout(ctx, rus.T()) - rus.doVisit(rus.T(), HomeBaseURL) - rus.verifyIsHome(ctx, rus.T()) +func (s *RedirectionURLScenario) SetupTest() { + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) } -func (rus *RedirectionURLScenario) TestShouldVerifyCustomURLParametersArePropagatedAfterRedirection() { +func (s *RedirectionURLScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() +} + +func (s *RedirectionURLScenario) TestShouldVerifyCustomURLParametersArePropagatedAfterRedirection() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html?myparam=test", SingleFactorBaseURL) - rus.doLoginOneFactor(ctx, rus.T(), "john", "password", false, targetURL) - rus.verifySecretAuthorized(ctx, rus.T()) - rus.verifyURLIs(ctx, rus.T(), targetURL) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) + s.verifyURLIs(s.T(), s.Context(ctx), targetURL) } func TestRedirectionURLScenario(t *testing.T) { diff --git a/internal/suites/scenario_regulation_test.go b/internal/suites/scenario_regulation_test.go index a350b859..272e1a52 100644 --- a/internal/suites/scenario_regulation_test.go +++ b/internal/suites/scenario_regulation_test.go @@ -11,27 +11,27 @@ import ( ) type RegulationScenario struct { - *SeleniumSuite + *RodSuite } func NewRegulationScenario() *RegulationScenario { return &RegulationScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *RegulationScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *RegulationScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -39,47 +39,49 @@ func (s *RegulationScenario) TearDownSuite() { } func (s *RegulationScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *RegulationScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() { ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisitLoginPage(ctx, s.T(), "") - s.doFillLoginPageAndClick(ctx, s.T(), "john", "bad-password", false) - s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.") + s.doVisitLoginPage(s.T(), s.Context(ctx), "") + s.doFillLoginPageAndClick(s.T(), s.Context(ctx), "john", "bad-password", false) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.") for i := 0; i < 3; i++ { - err := s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("bad-password") + err := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("bad-password") require.NoError(s.T(), err) - err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click() + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left") require.NoError(s.T(), err) - time.Sleep(1 * time.Second) } // Enter the correct password and test the regulation lock out - err := s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password") + err := s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("password") require.NoError(s.T(), err) - err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click() + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left") require.NoError(s.T(), err) - s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.") + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.") - time.Sleep(1 * time.Second) - s.verifyIsFirstFactorPage(ctx, s.T()) - time.Sleep(9 * time.Second) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) + time.Sleep(10 * time.Second) // Enter the correct password and test a successful login - err = s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password") + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "password-textfield").Input("password") require.NoError(s.T(), err) - err = s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click() + err = s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "sign-in-button").Click("left") require.NoError(s.T(), err) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) } func TestBlacklistingScenario(t *testing.T) { diff --git a/internal/suites/scenario_reset_password_test.go b/internal/suites/scenario_reset_password_test.go index 20354661..755deb7a 100644 --- a/internal/suites/scenario_reset_password_test.go +++ b/internal/suites/scenario_reset_password_test.go @@ -10,25 +10,25 @@ import ( ) type ResetPasswordScenario struct { - *SeleniumSuite + *RodSuite } func NewResetPasswordScenario() *ResetPasswordScenario { - return &ResetPasswordScenario{SeleniumSuite: new(SeleniumSuite)} + return &ResetPasswordScenario{RodSuite: new(RodSuite)} } func (s *ResetPasswordScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *ResetPasswordScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -36,64 +36,74 @@ func (s *ResetPasswordScenario) TearDownSuite() { } func (s *ResetPasswordScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *ResetPasswordScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *ResetPasswordScenario) TestShouldResetPassword() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) // Reset the password to abc - s.doResetPassword(ctx, s.T(), "john", "abc", "abc", false) + s.doResetPassword(s.T(), s.Context(ctx), "john", "abc", "abc", false) // Try to login with the old password - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Incorrect username or password.") // Try to login with the new password - s.doLoginOneFactor(ctx, s.T(), "john", "abc", false, "") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "abc", false, "") // Logout - s.doLogout(ctx, s.T()) + s.doLogout(s.T(), s.Context(ctx)) // Reset the original password - s.doResetPassword(ctx, s.T(), "john", "password", "password", false) + s.doResetPassword(s.T(), s.Context(ctx), "john", "password", "password", false) } func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitiated() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) // Try to initiate a password reset of an nonexistent user. - s.doInitiatePasswordReset(ctx, s.T(), "i_dont_exist") + s.doInitiatePasswordReset(s.T(), s.Context(ctx), "i_dont_exist") // Check that the notification make the attacker thinks the process is initiated - s.verifyMailNotificationDisplayed(ctx, s.T()) + s.verifyMailNotificationDisplayed(s.T(), s.Context(ctx)) } func (s *ResetPasswordScenario) TestShouldLetUserNoticeThereIsAPasswordMismatch() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) - s.doInitiatePasswordReset(ctx, s.T(), "john") - s.verifyMailNotificationDisplayed(ctx, s.T()) + s.doInitiatePasswordReset(s.T(), s.Context(ctx), "john") + s.verifyMailNotificationDisplayed(s.T(), s.Context(ctx)) - s.doCompletePasswordReset(ctx, s.T(), "password", "another_password") - s.verifyNotificationDisplayed(ctx, s.T(), "Passwords do not match.") + s.doCompletePasswordReset(s.T(), s.Context(ctx), "password", "another_password") + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Passwords do not match.") } func TestRunResetPasswordScenario(t *testing.T) { diff --git a/internal/suites/scenario_signin_email_test.go b/internal/suites/scenario_signin_email_test.go index 9b1ea897..09e9eb64 100644 --- a/internal/suites/scenario_signin_email_test.go +++ b/internal/suites/scenario_signin_email_test.go @@ -13,27 +13,27 @@ import ( ) type SigninEmailScenario struct { - *SeleniumSuite + *RodSuite } func NewSigninEmailScenario() *SigninEmailScenario { return &SigninEmailScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *SigninEmailScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *SigninEmailScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -41,21 +41,25 @@ func (s *SigninEmailScenario) TearDownSuite() { } func (s *SigninEmailScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *SigninEmailScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *SigninEmailScenario) TestShouldSignInWithUserEmail() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() targetURL := fmt.Sprintf("%s/secret.html", SingleFactorBaseURL) - s.doLoginOneFactor(ctx, s.T(), "john.doe@authelia.com", "password", false, targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john.doe@authelia.com", "password", false, targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func TestSigninEmailScenario(t *testing.T) { diff --git a/internal/suites/scenario_two_factor_test.go b/internal/suites/scenario_two_factor_test.go index 9ff5c6e2..79288467 100644 --- a/internal/suites/scenario_two_factor_test.go +++ b/internal/suites/scenario_two_factor_test.go @@ -11,27 +11,27 @@ import ( ) type TwoFactorSuite struct { - *SeleniumSuite + *RodSuite } func NewTwoFactorScenario() *TwoFactorSuite { return &TwoFactorSuite{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *TwoFactorSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *TwoFactorSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -39,62 +39,57 @@ func (s *TwoFactorSuite) TearDownSuite() { } func (s *TwoFactorSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *TwoFactorSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *TwoFactorSuite) TestShouldAuthorizeSecretAfterTwoFactor() { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() username := testUsername password := testPassword - // Login one factor - s.doLoginOneFactor(ctx, s.T(), username, password, false, "") - - // Check he reaches the 2FA stage - s.verifyIsSecondFactorPage(ctx, s.T()) - - // Then register the TOTP factor - secret := s.doRegisterTOTP(ctx, s.T()) - - // And logout - s.doLogout(ctx, s.T()) - - // Login again with 1FA & 2FA + // Login and register TOTP, logout and login again with 1FA & 2FA targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) - s.doLoginTwoFactor(ctx, s.T(), testUsername, testPassword, false, secret, targetURL) + _ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), username, password, false, targetURL) // And check if the user is redirected to the secret. - s.verifySecretAuthorized(ctx, s.T()) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) // Leave the secret - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // And try to reload it again to check the session is kept - s.doVisit(s.T(), targetURL) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), targetURL) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func (s *TwoFactorSuite) TestShouldFailTwoFactor() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() // Register TOTP secret and logout. - s.doRegisterThenLogout(ctx, s.T(), testUsername, testPassword) + s.doRegisterThenLogout(s.T(), s.Context(ctx), testUsername, testPassword) wrongPasscode := "123456" - s.doLoginOneFactor(ctx, s.T(), testUsername, testPassword, false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) - s.doEnterOTP(ctx, s.T(), wrongPasscode) - s.verifyNotificationDisplayed(ctx, s.T(), "The one-time password might be wrong") + s.doLoginOneFactor(s.T(), s.Context(ctx), testUsername, testPassword, false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.doEnterOTP(s.T(), s.Context(ctx), wrongPasscode) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "The one-time password might be wrong") } func TestRunTwoFactor(t *testing.T) { diff --git a/internal/suites/scenario_user_preferences_test.go b/internal/suites/scenario_user_preferences_test.go index b9620dd8..4d51509f 100644 --- a/internal/suites/scenario_user_preferences_test.go +++ b/internal/suites/scenario_user_preferences_test.go @@ -10,27 +10,27 @@ import ( ) type UserPreferencesScenario struct { - *SeleniumSuite + *RodSuite } func NewUserPreferencesScenario() *UserPreferencesScenario { return &UserPreferencesScenario{ - SeleniumSuite: new(SeleniumSuite), + RodSuite: new(RodSuite), } } func (s *UserPreferencesScenario) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *UserPreferencesScenario) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -38,59 +38,63 @@ func (s *UserPreferencesScenario) TearDownSuite() { } func (s *UserPreferencesScenario) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *UserPreferencesScenario) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *UserPreferencesScenario) TestShouldRememberLastUsed2FAMethod() { - ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() // Authenticate - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) // Then switch to push notification method - s.doChangeMethod(ctx, s.T(), "push-notification") - s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method") + s.doChangeMethod(s.T(), s.Context(ctx), "push-notification") + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method") // Switch context to clean up state in portal. - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Then go back to portal. - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) // And check the latest method is still used. - s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method") + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method") // Meaning the authentication is successful - s.verifyIsHome(ctx, s.T()) + s.verifyIsHome(s.T(), s.Context(ctx)) // Logout the user and see what user 'harry' sees. - s.doLogout(ctx, s.T()) - s.doLoginOneFactor(ctx, s.T(), "harry", "password", false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) - s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method") + s.doLogout(s.T(), s.Context(ctx)) + s.doLoginOneFactor(s.T(), s.Context(ctx), "harry", "password", false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method") - s.doLogout(ctx, s.T()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doLogout(s.T(), s.Context(ctx)) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) // Then log back as previous user and verify the push notification is still the default method - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) - s.WaitElementLocatedByID(ctx, s.T(), "push-notification-method") - s.verifyIsHome(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "push-notification-method") + s.verifyIsHome(s.T(), s.Context(ctx)) - s.doLogout(ctx, s.T()) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") + s.doLogout(s.T(), s.Context(ctx)) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") // Eventually restore the default method - s.doChangeMethod(ctx, s.T(), "one-time-password") - s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method") + s.doChangeMethod(s.T(), s.Context(ctx), "one-time-password") + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method") } func TestUserPreferencesScenario(t *testing.T) { diff --git a/internal/suites/suite_activedirectory_test.go b/internal/suites/suite_activedirectory_test.go index c99bf479..48e7f9b0 100644 --- a/internal/suites/suite_activedirectory_test.go +++ b/internal/suites/suite_activedirectory_test.go @@ -7,11 +7,11 @@ import ( ) type ActiveDirectorySuite struct { - *SeleniumSuite + *RodSuite } func NewActiveDirectorySuite() *ActiveDirectorySuite { - return &ActiveDirectorySuite{SeleniumSuite: new(SeleniumSuite)} + return &ActiveDirectorySuite{RodSuite: new(RodSuite)} } func (s *ActiveDirectorySuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_bypass_all_test.go b/internal/suites/suite_bypass_all_test.go index c6ae052b..4450d620 100644 --- a/internal/suites/suite_bypass_all_test.go +++ b/internal/suites/suite_bypass_all_test.go @@ -11,40 +11,53 @@ import ( ) type BypassAllWebDriverSuite struct { - *SeleniumSuite + *RodSuite } func NewBypassAllWebDriverSuite() *BypassAllWebDriverSuite { - return &BypassAllWebDriverSuite{SeleniumSuite: new(SeleniumSuite)} + return &BypassAllWebDriverSuite{RodSuite: new(RodSuite)} } func (s *BypassAllWebDriverSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *BypassAllWebDriverSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) } } +func (s *BypassAllWebDriverSuite) SetupTest() { + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} + +func (s *BypassAllWebDriverSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() +} + func (s *BypassAllWebDriverSuite) TestShouldAccessPublicResource() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", AdminBaseURL)) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", AdminBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) - s.doVisit(s.T(), fmt.Sprintf("%s/secret.html", PublicBaseURL)) - s.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", PublicBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } type BypassAllSuite struct { diff --git a/internal/suites/suite_docker_test.go b/internal/suites/suite_docker_test.go index 730735f9..8b6f23b7 100644 --- a/internal/suites/suite_docker_test.go +++ b/internal/suites/suite_docker_test.go @@ -7,11 +7,11 @@ import ( ) type DockerSuite struct { - *SeleniumSuite + *RodSuite } func NewDockerSuite() *DockerSuite { - return &DockerSuite{SeleniumSuite: new(SeleniumSuite)} + return &DockerSuite{RodSuite: new(RodSuite)} } func (s *DockerSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_duo_push_test.go b/internal/suites/suite_duo_push_test.go index 3a7eaf25..05dcbf78 100644 --- a/internal/suites/suite_duo_push_test.go +++ b/internal/suites/suite_duo_push_test.go @@ -10,25 +10,25 @@ import ( ) type DuoPushWebDriverSuite struct { - *SeleniumSuite + *RodSuite } func NewDuoPushWebDriverSuite() *DuoPushWebDriverSuite { - return &DuoPushWebDriverSuite{SeleniumSuite: new(SeleniumSuite)} + return &DuoPushWebDriverSuite{RodSuite: new(RodSuite)} } func (s *DuoPushWebDriverSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *DuoPushWebDriverSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -36,65 +36,75 @@ func (s *DuoPushWebDriverSuite) TearDownSuite() { } func (s *DuoPushWebDriverSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - s.doLogout(ctx, s.T()) + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) } func (s *DuoPushWebDriverSuite) TearDownTest() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) - s.doLogout(ctx, s.T()) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) - s.doChangeMethod(ctx, s.T(), "one-time-password") - s.WaitElementLocatedByID(ctx, s.T(), "one-time-password-method") + s.collectCoverage(s.Page) + s.MustClose() + }() + + s.doLogout(s.T(), s.Context(ctx)) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) + s.doChangeMethod(s.T(), s.Context(ctx), "one-time-password") + s.WaitElementLocatedByCSSSelector(s.T(), s.Context(ctx), "one-time-password-method") } func (s *DuoPushWebDriverSuite) TestShouldSucceedAuthentication() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() ConfigureDuo(s.T(), Allow) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.doChangeMethod(ctx, s.T(), "push-notification") - s.verifyIsHome(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.doChangeMethod(s.T(), s.Context(ctx), "push-notification") + s.verifyIsHome(s.T(), s.Context(ctx)) } func (s *DuoPushWebDriverSuite) TestShouldFailAuthentication() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() ConfigureDuo(s.T(), Deny) - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.doChangeMethod(ctx, s.T(), "push-notification") - s.WaitElementLocatedByClassName(ctx, s.T(), "failure-icon") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.doChangeMethod(s.T(), s.Context(ctx), "push-notification") + s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "failure-icon") } type DuoPushDefaultRedirectionSuite struct { - *SeleniumSuite + *RodSuite } func NewDuoPushDefaultRedirectionSuite() *DuoPushDefaultRedirectionSuite { - return &DuoPushDefaultRedirectionSuite{SeleniumSuite: new(SeleniumSuite)} + return &DuoPushDefaultRedirectionSuite{RodSuite: new(RodSuite)} } func (s *DuoPushDefaultRedirectionSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *DuoPushDefaultRedirectionSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -102,19 +112,25 @@ func (s *DuoPushDefaultRedirectionSuite) TearDownSuite() { } func (s *DuoPushDefaultRedirectionSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) +func (s *DuoPushDefaultRedirectionSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *DuoPushDefaultRedirectionSuite) TestUserIsRedirectedToDefaultURL() { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.doChangeMethod(ctx, s.T(), "push-notification") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.doChangeMethod(s.T(), s.Context(ctx), "push-notification") + s.verifyIsHome(s.T(), s.Page) } type DuoPushSuite struct { diff --git a/internal/suites/suite_haproxy.go b/internal/suites/suite_haproxy.go index a571a47f..160d1b9e 100644 --- a/internal/suites/suite_haproxy.go +++ b/internal/suites/suite_haproxy.go @@ -42,6 +42,13 @@ func init() { fmt.Println(frontendLogs) + haproxyLogs, err := dockerEnvironment.Logs("haproxy", nil) + if err != nil { + return err + } + + fmt.Println(haproxyLogs) + return nil } diff --git a/internal/suites/suite_haproxy_test.go b/internal/suites/suite_haproxy_test.go index 276214a3..1c66242b 100644 --- a/internal/suites/suite_haproxy_test.go +++ b/internal/suites/suite_haproxy_test.go @@ -7,11 +7,11 @@ import ( ) type HAProxySuite struct { - *SeleniumSuite + *RodSuite } func NewHAProxySuite() *HAProxySuite { - return &HAProxySuite{SeleniumSuite: new(SeleniumSuite)} + return &HAProxySuite{RodSuite: new(RodSuite)} } func (s *HAProxySuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_high_availability_test.go b/internal/suites/suite_high_availability_test.go index ce5f6913..b18bcfa6 100644 --- a/internal/suites/suite_high_availability_test.go +++ b/internal/suites/suite_high_availability_test.go @@ -13,25 +13,25 @@ import ( ) type HighAvailabilityWebDriverSuite struct { - *SeleniumSuite + *RodSuite } func NewHighAvailabilityWebDriverSuite() *HighAvailabilityWebDriverSuite { - return &HighAvailabilityWebDriverSuite{SeleniumSuite: new(SeleniumSuite)} + return &HighAvailabilityWebDriverSuite{RodSuite: new(RodSuite)} } func (s *HighAvailabilityWebDriverSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *HighAvailabilityWebDriverSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -39,37 +39,42 @@ func (s *HighAvailabilityWebDriverSuite) TearDownSuite() { } func (s *HighAvailabilityWebDriverSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *HighAvailabilityWebDriverSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActive() { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") err := haDockerEnvironment.Restart("redis-node-0") s.Require().NoError(err) - time.Sleep(5 * time.Second) - - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) } func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisNodeFailure() { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) err := haDockerEnvironment.Stop("redis-node-0") s.Require().NoError(err) @@ -79,32 +84,32 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrim s.Require().NoError(err) }() - // Allow fail over to occur. - time.Sleep(3 * time.Second) - - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Verify the user is still authenticated - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) // Then logout and login again to check we can see the secret. - s.doLogout(ctx, s.T()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doLogout(s.T(), s.Context(ctx)) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL)) - s.verifySecretAuthorized(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisSentinelFailureAndSecondaryRedisNodeFailure() { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) err := haDockerEnvironment.Stop("redis-sentinel-0") s.Require().NoError(err) @@ -122,38 +127,39 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrim s.Require().NoError(err) }() - // Allow fail over to occur. - time.Sleep(3 * time.Second) - - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Verify the user is still authenticated - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) } func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password") + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") err := haDockerEnvironment.Restart("mariadb") s.Require().NoError(err) - time.Sleep(20 * time.Second) - - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) } func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaRestart() { ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - secret := s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "") - s.verifyIsSecondFactorPage(ctx, s.T()) + secret := s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) err := haDockerEnvironment.Restart("authelia-backend") s.Require().NoError(err) @@ -161,19 +167,19 @@ func (s *HighAvailabilityWebDriverSuite) TestShouldKeepSessionAfterAutheliaResta err = waitUntilAutheliaBackendIsReady(haDockerEnvironment) s.Require().NoError(err) - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Verify the user is still authenticated - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsSecondFactorPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsSecondFactorPage(s.T(), s.Context(ctx)) // Then logout and login again to check the secret is still there - s.doLogout(ctx, s.T()) - s.verifyIsFirstFactorPage(ctx, s.T()) + s.doLogout(s.T(), s.Context(ctx)) + s.verifyIsFirstFactorPage(s.T(), s.Context(ctx)) - s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL)) - s.verifySecretAuthorized(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } var UserJohn = "john" @@ -220,31 +226,34 @@ var expectedAuthorizations = map[string](map[string]bool){ } func (s *HighAvailabilityWebDriverSuite) TestShouldVerifyAccessControl() { - verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, username, targetURL string, authorized bool) { //nolint:unparam - s.doVisit(t, targetURL) - s.verifyURLIs(ctx, t, targetURL) + verifyUserIsAuthorized := func(ctx context.Context, t *testing.T, targetURL string, authorized bool) { + s.doVisit(t, s.Context(ctx), targetURL) + s.verifyURLIs(t, s.Context(ctx), targetURL) if authorized { - s.verifySecretAuthorized(ctx, t) + s.verifySecretAuthorized(t, s.Context(ctx)) } else { - s.verifyBodyContains(ctx, t, "403 Forbidden") + s.verifyBodyContains(t, s.Context(ctx), "403 Forbidden") } } verifyAuthorization := func(username string) func(t *testing.T) { return func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + s.collectScreenshot(ctx.Err(), s.Page) + cancel() + }() - s.doRegisterAndLogin2FA(ctx, t, username, "password", false, "") + s.doRegisterAndLogin2FA(t, s.Context(ctx), username, "password", false, "") for url, authorizations := range expectedAuthorizations { t.Run(url, func(t *testing.T) { - verifyUserIsAuthorized(ctx, t, username, url, authorizations[username]) + verifyUserIsAuthorized(ctx, t, url, authorizations[username]) }) } - s.doLogout(ctx, t) + s.doLogout(t, s.Context(ctx)) } } diff --git a/internal/suites/suite_kubernetes_test.go b/internal/suites/suite_kubernetes_test.go index 8cb61c71..a40889dd 100644 --- a/internal/suites/suite_kubernetes_test.go +++ b/internal/suites/suite_kubernetes_test.go @@ -7,11 +7,11 @@ import ( ) type KubernetesSuite struct { - *SeleniumSuite + *RodSuite } func NewKubernetesSuite() *KubernetesSuite { - return &KubernetesSuite{SeleniumSuite: new(SeleniumSuite)} + return &KubernetesSuite{RodSuite: new(RodSuite)} } func (s *KubernetesSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_ldap_test.go b/internal/suites/suite_ldap_test.go index 5859d936..96334f8a 100644 --- a/internal/suites/suite_ldap_test.go +++ b/internal/suites/suite_ldap_test.go @@ -7,11 +7,11 @@ import ( ) type LDAPSuite struct { - *SeleniumSuite + *RodSuite } func NewLDAPSuite() *LDAPSuite { - return &LDAPSuite{SeleniumSuite: new(SeleniumSuite)} + return &LDAPSuite{RodSuite: new(RodSuite)} } func (s *LDAPSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_mariadb_test.go b/internal/suites/suite_mariadb_test.go index cae16a50..eb201aa6 100644 --- a/internal/suites/suite_mariadb_test.go +++ b/internal/suites/suite_mariadb_test.go @@ -7,11 +7,11 @@ import ( ) type MariadbSuite struct { - *SeleniumSuite + *RodSuite } func NewMariadbSuite() *MariadbSuite { - return &MariadbSuite{SeleniumSuite: new(SeleniumSuite)} + return &MariadbSuite{RodSuite: new(RodSuite)} } func (s *MariadbSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_mysql_test.go b/internal/suites/suite_mysql_test.go index e6e6c6f7..1a634ad2 100644 --- a/internal/suites/suite_mysql_test.go +++ b/internal/suites/suite_mysql_test.go @@ -7,11 +7,11 @@ import ( ) type MySQLSuite struct { - *SeleniumSuite + *RodSuite } func NewMySQLSuite() *MySQLSuite { - return &MySQLSuite{SeleniumSuite: new(SeleniumSuite)} + return &MySQLSuite{RodSuite: new(RodSuite)} } func (s *MySQLSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_network_acl_test.go b/internal/suites/suite_network_acl_test.go index a8562a75..82b1e90d 100644 --- a/internal/suites/suite_network_acl_test.go +++ b/internal/suites/suite_network_acl_test.go @@ -21,20 +21,21 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - wds, err := StartWebDriver() + browser, err := StartRod() s.Require().NoError(err) defer func() { - err = wds.Stop() + err = browser.WebDriver.Close() s.Require().NoError(err) + browser.Launcher.Cleanup() }() targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) - wds.doVisit(s.T(), targetURL) - wds.verifyIsFirstFactorPage(ctx, s.T()) + page := browser.doCreateTab(s.T(), targetURL).Context(ctx) - wds.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, targetURL) - wds.verifySecretAuthorized(ctx, s.T()) + browser.verifyIsFirstFactorPage(s.T(), page) + browser.doRegisterAndLogin2FA(s.T(), page, "john", "password", false, targetURL) + browser.verifySecretAuthorized(s.T(), page) } // from network 192.168.240.201/32. @@ -42,21 +43,22 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", GetWebDriverPort()) + browser, err := StartRodWithProxy("http://proxy-client1.example.com:3128") s.Require().NoError(err) defer func() { - err = wds.Stop() + err = browser.WebDriver.Close() s.Require().NoError(err) + browser.Launcher.Cleanup() }() targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL) - wds.doVisit(s.T(), targetURL) - wds.verifyIsFirstFactorPage(ctx, s.T()) + page := browser.doCreateTab(s.T(), targetURL).Context(ctx) - wds.doLoginOneFactor(ctx, s.T(), "john", "password", + browser.verifyIsFirstFactorPage(s.T(), page) + browser.doLoginOneFactor(s.T(), page, "john", "password", false, fmt.Sprintf("%s/secret.html", SecureBaseURL)) - wds.verifySecretAuthorized(ctx, s.T()) + browser.verifySecretAuthorized(s.T(), page) } // from network 192.168.240.202/32. @@ -64,16 +66,18 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", GetWebDriverPort()) + browser, err := StartRodWithProxy("http://proxy-client2.example.com:3128") s.Require().NoError(err) defer func() { - err = wds.Stop() + err = browser.WebDriver.Close() s.Require().NoError(err) + browser.Launcher.Cleanup() }() - wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) - wds.verifySecretAuthorized(ctx, s.T()) + page := browser.doCreateTab(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)).Context(ctx) + + browser.verifySecretAuthorized(s.T(), page) } func TestNetworkACLSuite(t *testing.T) { diff --git a/internal/suites/suite_oidc_test.go b/internal/suites/suite_oidc_test.go index 4c6326e0..acf224dd 100644 --- a/internal/suites/suite_oidc_test.go +++ b/internal/suites/suite_oidc_test.go @@ -7,11 +7,11 @@ import ( ) type OIDCSuite struct { - *SeleniumSuite + *RodSuite } func NewOIDCSuite() *OIDCSuite { - return &OIDCSuite{SeleniumSuite: new(SeleniumSuite)} + return &OIDCSuite{RodSuite: new(RodSuite)} } func (s *OIDCSuite) TestOIDCScenario() { diff --git a/internal/suites/suite_oidc_traefik_test.go b/internal/suites/suite_oidc_traefik_test.go index 38b8292a..eae84c4d 100644 --- a/internal/suites/suite_oidc_traefik_test.go +++ b/internal/suites/suite_oidc_traefik_test.go @@ -7,11 +7,11 @@ import ( ) type OIDCTraefikSuite struct { - *SeleniumSuite + *RodSuite } func NewOIDCTraefikSuite() *OIDCTraefikSuite { - return &OIDCTraefikSuite{SeleniumSuite: new(SeleniumSuite)} + return &OIDCTraefikSuite{RodSuite: new(RodSuite)} } func (s *OIDCTraefikSuite) TestOIDCScenario() { diff --git a/internal/suites/suite_one_factor_only_test.go b/internal/suites/suite_one_factor_only_test.go index 4b1971fd..1588245e 100644 --- a/internal/suites/suite_one_factor_only_test.go +++ b/internal/suites/suite_one_factor_only_test.go @@ -15,25 +15,25 @@ type OneFactorOnlySuite struct { } type OneFactorOnlyWebSuite struct { - *SeleniumSuite + *RodSuite } func NewOneFactorOnlyWebSuite() *OneFactorOnlyWebSuite { - return &OneFactorOnlyWebSuite{SeleniumSuite: new(SeleniumSuite)} + return &OneFactorOnlyWebSuite{RodSuite: new(RodSuite)} } func (s *OneFactorOnlyWebSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *OneFactorOnlyWebSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -41,62 +41,81 @@ func (s *OneFactorOnlyWebSuite) TearDownSuite() { } func (s *OneFactorOnlyWebSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) +func (s *OneFactorOnlyWebSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } // No target url is provided, then the user should be redirect to the default url. func (s *OneFactorOnlyWebSuite) TestShouldRedirectUserToDefaultURL() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsHome(s.T(), s.Context(ctx)) } // Unsafe URL is provided, then the user should be redirect to the default url. func (s *OneFactorOnlyWebSuite) TestShouldRedirectUserToDefaultURLWhenURLIsUnsafe() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "http://unsafe.local") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "http://unsafe.local") + s.verifyIsHome(s.T(), s.Context(ctx)) } // When use logged in and visit the portal again, she gets redirect to the authenticated view. func (s *OneFactorOnlyWebSuite) TestShouldDisplayAuthenticatedView() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsAuthenticatedPage(ctx, s.T()) + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsHome(s.T(), s.Context(ctx)) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsAuthenticatedPage(s.T(), s.Context(ctx)) } func (s *OneFactorOnlyWebSuite) TestShouldRedirectAlreadyAuthenticatedUser() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsHome(s.T(), s.Context(ctx)) - s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://singlefactor.example.com:8080/secret.html", GetLoginBaseURL())) - s.verifyURLIs(ctx, s.T(), "https://singlefactor.example.com:8080/secret.html") + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://singlefactor.example.com:8080/secret.html", GetLoginBaseURL())) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) + s.verifyURLIs(s.T(), s.Context(ctx), "https://singlefactor.example.com:8080/secret.html") } func (s *OneFactorOnlyWebSuite) TestShouldNotRedirectAlreadyAuthenticatedUserToUnsafeURL() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") - s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") + s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, "") + s.verifyIsHome(s.T(), s.Context(ctx)) // Visit the login page and wait for redirection to 2FA page with success icon displayed. - s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL())) - s.verifyNotificationDisplayed(ctx, s.T(), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.") + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL())) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.") } func (s *OneFactorOnlySuite) TestWeb() { diff --git a/internal/suites/suite_pathprefix_test.go b/internal/suites/suite_pathprefix_test.go index e2dd8a53..098897af 100644 --- a/internal/suites/suite_pathprefix_test.go +++ b/internal/suites/suite_pathprefix_test.go @@ -7,11 +7,11 @@ import ( ) type PathPrefixSuite struct { - *SeleniumSuite + *RodSuite } func NewPathPrefixSuite() *PathPrefixSuite { - return &PathPrefixSuite{SeleniumSuite: new(SeleniumSuite)} + return &PathPrefixSuite{RodSuite: new(RodSuite)} } func (s *PathPrefixSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_postgres_test.go b/internal/suites/suite_postgres_test.go index 8ee97c2f..616702f5 100644 --- a/internal/suites/suite_postgres_test.go +++ b/internal/suites/suite_postgres_test.go @@ -7,11 +7,11 @@ import ( ) type PostgresSuite struct { - *SeleniumSuite + *RodSuite } func NewPostgresSuite() *PostgresSuite { - return &PostgresSuite{SeleniumSuite: new(SeleniumSuite)} + return &PostgresSuite{RodSuite: new(RodSuite)} } func (s *PostgresSuite) TestOneFactorScenario() { diff --git a/internal/suites/suite_short_timeouts_test.go b/internal/suites/suite_short_timeouts_test.go index 49df7cca..f59ebdce 100644 --- a/internal/suites/suite_short_timeouts_test.go +++ b/internal/suites/suite_short_timeouts_test.go @@ -7,11 +7,11 @@ import ( ) type ShortTimeoutsSuite struct { - *SeleniumSuite + *RodSuite } func NewShortTimeoutsSuite() *ShortTimeoutsSuite { - return &ShortTimeoutsSuite{SeleniumSuite: new(SeleniumSuite)} + return &ShortTimeoutsSuite{RodSuite: new(RodSuite)} } func (s *ShortTimeoutsSuite) TestDefaultRedirectionURLScenario() { diff --git a/internal/suites/suite_standalone_test.go b/internal/suites/suite_standalone_test.go index 0049b8c5..6f2ce907 100644 --- a/internal/suites/suite_standalone_test.go +++ b/internal/suites/suite_standalone_test.go @@ -18,25 +18,25 @@ import ( ) type StandaloneWebDriverSuite struct { - *SeleniumSuite + *RodSuite } func NewStandaloneWebDriverSuite() *StandaloneWebDriverSuite { - return &StandaloneWebDriverSuite{SeleniumSuite: new(SeleniumSuite)} + return &StandaloneWebDriverSuite{RodSuite: new(RodSuite)} } func (s *StandaloneWebDriverSuite) SetupSuite() { - wds, err := StartWebDriver() + browser, err := StartRod() if err != nil { log.Fatal(err) } - s.WebDriverSession = wds + s.RodSession = browser } func (s *StandaloneWebDriverSuite) TearDownSuite() { - err := s.WebDriverSession.Stop() + err := s.RodSession.Stop() if err != nil { log.Fatal(err) @@ -44,62 +44,78 @@ func (s *StandaloneWebDriverSuite) TearDownSuite() { } func (s *StandaloneWebDriverSuite) SetupTest() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) +} - s.doLogout(ctx, s.T()) - s.WebDriverSession.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) +func (s *StandaloneWebDriverSuite) TearDownTest() { + s.collectCoverage(s.Page) + s.MustClose() } func (s *StandaloneWebDriverSuite) TestShouldLetUserKnowHeIsAlreadyAuthenticated() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - _ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "") + _ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "") // Visit home page to change context. - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Visit the login page and wait for redirection to 2FA page with success icon displayed. - s.doVisit(s.T(), GetLoginBaseURL()) - s.verifyIsAuthenticatedPage(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), GetLoginBaseURL()) + s.verifyIsAuthenticatedPage(s.T(), s.Context(ctx)) } func (s *StandaloneWebDriverSuite) TestShouldRedirectAlreadyAuthenticatedUser() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - _ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "") + _ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "") // Visit home page to change context. - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Visit the login page and wait for redirection to 2FA page with success icon displayed. - s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.com:8080", GetLoginBaseURL())) - s.verifyURLIs(ctx, s.T(), "https://secure.example.com:8080/") + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.com:8080", GetLoginBaseURL())) + + _, err := s.Page.ElementR("h1", "Public resource") + require.NoError(s.T(), err) + s.verifyURLIs(s.T(), s.Context(ctx), "https://secure.example.com:8080/") } func (s *StandaloneWebDriverSuite) TestShouldNotRedirectAlreadyAuthenticatedUserToUnsafeURL() { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() - _ = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, "") + _ = s.doRegisterAndLogin2FA(s.T(), s.Context(ctx), "john", "password", false, "") // Visit home page to change context. - s.doVisit(s.T(), HomeBaseURL) - s.verifyIsHome(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), HomeBaseURL) + s.verifyIsHome(s.T(), s.Context(ctx)) // Visit the login page and wait for redirection to 2FA page with success icon displayed. - s.doVisit(s.T(), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL())) - s.verifyNotificationDisplayed(ctx, s.T(), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.") + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s?rd=https://secure.example.local:8080", GetLoginBaseURL())) + s.verifyNotificationDisplayed(s.T(), s.Context(ctx), "Redirection was determined to be unsafe and aborted. Ensure the redirection URL is correct.") } func (s *StandaloneWebDriverSuite) TestShouldCheckUserIsAskedToRegisterDevice() { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer func() { + cancel() + s.collectScreenshot(ctx.Err(), s.Page) + }() username := "john" password := "password" @@ -109,21 +125,21 @@ func (s *StandaloneWebDriverSuite) TestShouldCheckUserIsAskedToRegisterDevice() require.NoError(s.T(), provider.DeleteTOTPSecret(username)) // Login one factor. - s.doLoginOneFactor(ctx, s.T(), username, password, false, "") + s.doLoginOneFactor(s.T(), s.Context(ctx), username, password, false, "") // Check the user is asked to register a new device. - s.WaitElementLocatedByClassName(ctx, s.T(), "state-not-registered") + s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-not-registered") // Then register the TOTP factor. - s.doRegisterTOTP(ctx, s.T()) + s.doRegisterTOTP(s.T(), s.Context(ctx)) // And logout. - s.doLogout(ctx, s.T()) + s.doLogout(s.T(), s.Context(ctx)) // Login one factor again. - s.doLoginOneFactor(ctx, s.T(), username, password, false, "") + s.doLoginOneFactor(s.T(), s.Context(ctx), username, password, false, "") // now the user should be asked to perform 2FA - s.WaitElementLocatedByClassName(ctx, s.T(), "state-method") + s.WaitElementLocatedByClassName(s.T(), s.Context(ctx), "state-method") } type StandaloneSuite struct { diff --git a/internal/suites/suite_traefik2_test.go b/internal/suites/suite_traefik2_test.go index 0f7e36d7..0cbddf11 100644 --- a/internal/suites/suite_traefik2_test.go +++ b/internal/suites/suite_traefik2_test.go @@ -10,11 +10,11 @@ import ( ) type Traefik2Suite struct { - *SeleniumSuite + *RodSuite } func NewTraefik2Suite() *Traefik2Suite { - return &Traefik2Suite{SeleniumSuite: new(SeleniumSuite)} + return &Traefik2Suite{RodSuite: new(RodSuite)} } func (s *Traefik2Suite) TestOneFactorScenario() { @@ -30,31 +30,34 @@ func (s *Traefik2Suite) TestCustomHeaders() { } func (s *Traefik2Suite) TestShouldKeepSessionAfterRedisRestart() { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - wds, err := StartWebDriver() - s.Require().NoError(err) - + ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) defer func() { - err = wds.Stop() + cancel() + s.collectCoverage(s.Page) + s.collectScreenshot(ctx.Err(), s.Page) + s.MustClose() + err := s.RodSession.Stop() s.Require().NoError(err) }() - secret := wds.doRegisterThenLogout(ctx, s.T(), "john", "password") + browser, err := StartRod() + s.Require().NoError(err) + s.RodSession = browser - wds.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") + s.Page = s.doCreateTab(s.T(), HomeBaseURL) + s.verifyIsHome(s.T(), s.Page) + secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") - wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) - wds.verifySecretAuthorized(ctx, s.T()) + s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") + + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", SecureBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) err = traefik2DockerEnvironment.Restart("redis") s.Require().NoError(err) - time.Sleep(5 * time.Second) - - wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL)) - wds.verifySecretAuthorized(ctx, s.T()) + s.doVisit(s.T(), s.Context(ctx), fmt.Sprintf("%s/secret.html", SecureBaseURL)) + s.verifySecretAuthorized(s.T(), s.Context(ctx)) } func TestTraefik2Suite(t *testing.T) { diff --git a/internal/suites/suite_traefik_test.go b/internal/suites/suite_traefik_test.go index 58147674..3c1fbc7a 100644 --- a/internal/suites/suite_traefik_test.go +++ b/internal/suites/suite_traefik_test.go @@ -7,11 +7,11 @@ import ( ) type TraefikSuite struct { - *SeleniumSuite + *RodSuite } func NewTraefikSuite() *TraefikSuite { - return &TraefikSuite{SeleniumSuite: new(SeleniumSuite)} + return &TraefikSuite{RodSuite: new(RodSuite)} } func (s *TraefikSuite) TestOneFactorScenario() { diff --git a/internal/suites/suites.go b/internal/suites/suites.go index c8f4c43a..4595cf52 100644 --- a/internal/suites/suites.go +++ b/internal/suites/suites.go @@ -1,15 +1,16 @@ package suites import ( + "github.com/go-rod/rod" "github.com/stretchr/testify/suite" - "github.com/tebeka/selenium" ) -// SeleniumSuite is a selenium suite. -type SeleniumSuite struct { +// RodSuite is a go-rod suite. +type RodSuite struct { suite.Suite - *WebDriverSession + *RodSession + *rod.Page } // CommandSuite is a command line interface suite. @@ -21,8 +22,3 @@ type CommandSuite struct { *DockerEnvironment } - -// WebDriver return the webdriver of the suite. -func (s *SeleniumSuite) WebDriver() selenium.WebDriver { - return s.WebDriverSession.WebDriver -} diff --git a/internal/suites/utils.go b/internal/suites/utils.go index 6e26e87b..d29e661d 100644 --- a/internal/suites/utils.go +++ b/internal/suites/utils.go @@ -1,11 +1,17 @@ package suites import ( + "context" + "fmt" "io/ioutil" + "log" "os" "path/filepath" - "strconv" + "runtime" "strings" + "time" + + "github.com/go-rod/rod" ) // GetLoginBaseURL returns the URL of the login portal and the path prefix if specified. @@ -17,16 +23,51 @@ func GetLoginBaseURL() string { return LoginBaseURL } -// GetWebDriverPort returns the port to initialize the webdriver with. -func GetWebDriverPort() int { - driverPort := os.Getenv("CHROMEDRIVER_PORT") - if driverPort == "" { - driverPort = defaultChromeDriverPort +func (rs *RodSession) collectCoverage(page *rod.Page) { + coverageDir := "../../web/.nyc_output" + now := time.Now() + + resp, err := page.Eval("JSON.stringify(window.__coverage__)") + if err != nil { + log.Fatal(err) } - p, _ := strconv.Atoi(driverPort) + coverageData := fmt.Sprintf("%v", resp.Value) - return p + _ = os.MkdirAll(coverageDir, 0775) + + if coverageData != "" { + err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, now.Unix()), []byte(coverageData), 0664) //nolint:gosec + if err != nil { + log.Fatal(err) + } + + err = filepath.Walk("../../web/.nyc_output", fixCoveragePath) + if err != nil { + log.Fatal(err) + } + } +} + +func (rs *RodSession) collectScreenshot(err error, page *rod.Page) { + if err == context.DeadlineExceeded && os.Getenv("CI") == stringTrue { + base := "/buildkite/screenshots" + build := os.Getenv("BUILDKITE_BUILD_NUMBER") + suite := strings.ToLower(os.Getenv("SUITE")) + job := os.Getenv("BUILDKITE_JOB_ID") + path := filepath.Join(fmt.Sprintf("%s/%s/%s/%s", base, build, suite, job)) //nolint: gocritic + + if err := os.MkdirAll(path, 0755); err != nil { + log.Fatal(err) + } + + pc, _, _, _ := runtime.Caller(2) + fn := runtime.FuncForPC(pc) + p := "github.com/authelia/authelia/v4/internal/suites." + r := strings.NewReplacer(p, "", "(", "", ")", "", "*", "", ".", "-") + + page.MustScreenshotFullPage(fmt.Sprintf("%s/%s.jpg", path, r.Replace(fn.Name()))) + } } func fixCoveragePath(path string, file os.FileInfo, err error) error { diff --git a/internal/suites/verify_body_contains.go b/internal/suites/verify_body_contains.go index 011e6366..8594c2bb 100644 --- a/internal/suites/verify_body_contains.go +++ b/internal/suites/verify_body_contains.go @@ -1,33 +1,29 @@ package suites import ( - "context" + "fmt" "strings" "testing" + "github.com/go-rod/rod" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tebeka/selenium" ) -func (wds *WebDriverSession) verifyBodyContains(ctx context.Context, t *testing.T, pattern string) { - err := wds.Wait(ctx, func(wd selenium.WebDriver) (bool, error) { - bodyElement, err := wds.WebDriver.FindElement(selenium.ByTagName, "body") +func (rs *RodSession) verifyBodyContains(t *testing.T, page *rod.Page, pattern string) { + body, err := page.Element("body") + assert.NoError(t, err) + assert.NotNil(t, body) - if err != nil { - return false, err - } + text, err := body.Text() + assert.NoError(t, err) + assert.NotNil(t, text) - if bodyElement == nil { - return false, nil - } + if strings.Contains(text, pattern) { + err = nil + } else { + err = fmt.Errorf("body does not contain pattern: %s", pattern) + } - content, err := bodyElement.Text() - - if err != nil { - return false, err - } - - return strings.Contains(content, pattern), nil - }) require.NoError(t, err) } diff --git a/internal/suites/verify_is_authenticated_page.go b/internal/suites/verify_is_authenticated_page.go index abe01df7..5cb24d4b 100644 --- a/internal/suites/verify_is_authenticated_page.go +++ b/internal/suites/verify_is_authenticated_page.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyIsAuthenticatedPage(ctx context.Context, t *testing.T) { - wds.WaitElementLocatedByID(ctx, t, "authenticated-stage") +func (rs *RodSession) verifyIsAuthenticatedPage(t *testing.T, page *rod.Page) { + rs.WaitElementLocatedByCSSSelector(t, page, "authenticated-stage") } diff --git a/internal/suites/verify_is_consent_page.go b/internal/suites/verify_is_consent_page.go index d737cfe1..de143a83 100644 --- a/internal/suites/verify_is_consent_page.go +++ b/internal/suites/verify_is_consent_page.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyIsConsentPage(ctx context.Context, t *testing.T) { - wds.WaitElementLocatedByID(ctx, t, "consent-stage") +func (rs *RodSession) verifyIsConsentPage(t *testing.T, page *rod.Page) { + rs.WaitElementLocatedByCSSSelector(t, page, "consent-stage") } diff --git a/internal/suites/verify_is_first_factor_page.go b/internal/suites/verify_is_first_factor_page.go index 3afe409c..208194e3 100644 --- a/internal/suites/verify_is_first_factor_page.go +++ b/internal/suites/verify_is_first_factor_page.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyIsFirstFactorPage(ctx context.Context, t *testing.T) { - wds.WaitElementLocatedByID(ctx, t, "first-factor-stage") +func (rs *RodSession) verifyIsFirstFactorPage(t *testing.T, page *rod.Page) { + rs.WaitElementLocatedByCSSSelector(t, page, "first-factor-stage") } diff --git a/internal/suites/verify_is_home.go b/internal/suites/verify_is_home.go index c1bdedf0..a3fa2701 100644 --- a/internal/suites/verify_is_home.go +++ b/internal/suites/verify_is_home.go @@ -1,11 +1,13 @@ package suites import ( - "context" "fmt" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyIsHome(ctx context.Context, t *testing.T) { - wds.verifyURLIs(ctx, t, fmt.Sprintf("%s/", HomeBaseURL)) +func (rs *RodSession) verifyIsHome(t *testing.T, page *rod.Page) { + page.MustElementR("h1", "Access the secret") + rs.verifyURLIs(t, page, fmt.Sprintf("%s/", HomeBaseURL)) } diff --git a/internal/suites/verify_is_oidc.go b/internal/suites/verify_is_oidc.go new file mode 100644 index 00000000..9e8e2219 --- /dev/null +++ b/internal/suites/verify_is_oidc.go @@ -0,0 +1,12 @@ +package suites + +import ( + "testing" + + "github.com/go-rod/rod" +) + +func (rs *RodSession) verifyIsOIDC(t *testing.T, page *rod.Page, pattern, url string) { + page.MustElementR("body", pattern) + rs.verifyURLIs(t, page, url) +} diff --git a/internal/suites/verify_is_public.go b/internal/suites/verify_is_public.go new file mode 100644 index 00000000..2a428d50 --- /dev/null +++ b/internal/suites/verify_is_public.go @@ -0,0 +1,13 @@ +package suites + +import ( + "fmt" + "testing" + + "github.com/go-rod/rod" +) + +func (rs *RodSession) verifyIsPublic(t *testing.T, page *rod.Page) { + page.MustElementR("body", "headers") + rs.verifyURLIs(t, page, fmt.Sprintf("%s/headers", PublicBaseURL)) +} diff --git a/internal/suites/verify_is_second_factor_page.go b/internal/suites/verify_is_second_factor_page.go index 8cc5c325..a37ffc27 100644 --- a/internal/suites/verify_is_second_factor_page.go +++ b/internal/suites/verify_is_second_factor_page.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyIsSecondFactorPage(ctx context.Context, t *testing.T) { - wds.WaitElementLocatedByID(ctx, t, "second-factor-stage") +func (rs *RodSession) verifyIsSecondFactorPage(t *testing.T, page *rod.Page) { + rs.WaitElementLocatedByCSSSelector(t, page, "second-factor-stage") } diff --git a/internal/suites/verify_mail.go b/internal/suites/verify_mail.go index 663594b8..95cc046e 100644 --- a/internal/suites/verify_mail.go +++ b/internal/suites/verify_mail.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifyMailNotificationDisplayed(ctx context.Context, t *testing.T) { - wds.verifyNotificationDisplayed(ctx, t, "An email has been sent to your address to complete the process.") +func (rs *RodSession) verifyMailNotificationDisplayed(t *testing.T, page *rod.Page) { + rs.verifyNotificationDisplayed(t, page, "An email has been sent to your address to complete the process.") } diff --git a/internal/suites/verify_notification.go b/internal/suites/verify_notification.go index 26dba479..262dfae1 100644 --- a/internal/suites/verify_notification.go +++ b/internal/suites/verify_notification.go @@ -1,14 +1,14 @@ package suites import ( - "context" "testing" + "github.com/go-rod/rod" "github.com/stretchr/testify/assert" ) -func (wds *WebDriverSession) verifyNotificationDisplayed(ctx context.Context, t *testing.T, message string) { - el := wds.WaitElementLocatedByClassName(ctx, t, "notification") +func (rs *RodSession) verifyNotificationDisplayed(t *testing.T, page *rod.Page, message string) { + el, err := page.ElementR(".notification", message) + assert.NoError(t, err) assert.NotNil(t, el) - wds.WaitElementTextContains(ctx, t, el, message) } diff --git a/internal/suites/verify_secret_authorized.go b/internal/suites/verify_secret_authorized.go index 4859e128..2c6c94f4 100644 --- a/internal/suites/verify_secret_authorized.go +++ b/internal/suites/verify_secret_authorized.go @@ -1,10 +1,11 @@ package suites import ( - "context" "testing" + + "github.com/go-rod/rod" ) -func (wds *WebDriverSession) verifySecretAuthorized(ctx context.Context, t *testing.T) { - wds.WaitElementLocatedByID(ctx, t, "secret") +func (rs *RodSession) verifySecretAuthorized(t *testing.T, page *rod.Page) { + rs.WaitElementLocatedByCSSSelector(t, page, "secret") } diff --git a/internal/suites/verify_url_is.go b/internal/suites/verify_url_is.go index db3848b0..10c59c62 100644 --- a/internal/suites/verify_url_is.go +++ b/internal/suites/verify_url_is.go @@ -1,23 +1,13 @@ package suites import ( - "context" "testing" + "github.com/go-rod/rod" "github.com/stretchr/testify/require" - "github.com/tebeka/selenium" ) -func (wds *WebDriverSession) verifyURLIs(ctx context.Context, t *testing.T, url string) { - err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { - currentURL, err := driver.CurrentURL() - - if err != nil { - return false, err - } - - return currentURL == url, nil - }) - - require.NoError(t, err) +func (rs *RodSession) verifyURLIs(t *testing.T, page *rod.Page, url string) { + currentURL := page.MustInfo().URL + require.Equal(t, url, currentURL, "they should be equal") } diff --git a/internal/suites/webdriver.go b/internal/suites/webdriver.go index 10620767..e93b2353 100644 --- a/internal/suites/webdriver.go +++ b/internal/suites/webdriver.go @@ -1,250 +1,115 @@ package suites import ( - "context" - "encoding/json" - "errors" "fmt" - "io/ioutil" "os" - "path/filepath" "strings" "testing" "time" - log "github.com/sirupsen/logrus" + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/launcher" "github.com/stretchr/testify/require" - "github.com/tebeka/selenium" - "github.com/tebeka/selenium/chrome" ) -// WebDriverSession binding a selenium service and a webdriver. -type WebDriverSession struct { - service *selenium.Service - WebDriver selenium.WebDriver +// RodSession binding a chrome session with devtool protocol. +type RodSession struct { + Launcher *launcher.Launcher + WebDriver *rod.Browser } -// StartWebDriverWithProxy create a selenium session. -func StartWebDriverWithProxy(proxy string, port int) (*WebDriverSession, error) { - driverPath := os.Getenv("CHROMEDRIVER_PATH") - if driverPath == "" { - driverPath = "/usr/bin/chromedriver" - } - - service, err := selenium.NewChromeDriverService(driverPath, port) - - if err != nil { - return nil, err - } - +// StartRodWithProxy create a rod/chromedp session. +func StartRodWithProxy(proxy string) (*RodSession, error) { browserPath := os.Getenv("BROWSER_PATH") if browserPath == "" { browserPath = "/usr/bin/chromium-browser" } - chromeCaps := chrome.Capabilities{ - Path: browserPath, - } - - chromeCaps.Args = append(chromeCaps.Args, "--ignore-certificate-errors") + headless := false + trace := true + motion := 0 * time.Second if os.Getenv("HEADLESS") != "" { - chromeCaps.Args = append(chromeCaps.Args, "--headless") - chromeCaps.Args = append(chromeCaps.Args, "--no-sandbox") + headless = true + trace = false + motion = 0 * time.Second } - if proxy != "" { - chromeCaps.Args = append(chromeCaps.Args, fmt.Sprintf("--proxy-server=%s", proxy)) - } + l := launcher.New(). + Bin(browserPath). + Proxy(proxy). + Headless(headless). + Devtools(true) + url := l.MustLaunch() - caps := selenium.Capabilities{} - caps.AddChrome(chromeCaps) + browser := rod.New(). + ControlURL(url). + Trace(trace). + SlowMotion(motion). + MustConnect() - wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port)) - if err != nil { - _ = service.Stop() + browser.MustIgnoreCertErrors(true) - log.Fatal(err) - } - - return &WebDriverSession{ - service: service, - WebDriver: wd, + return &RodSession{ + Launcher: l, + WebDriver: browser, }, nil } -// StartWebDriver create a selenium session. -func StartWebDriver() (*WebDriverSession, error) { - return StartWebDriverWithProxy("", GetWebDriverPort()) +// StartRod create a rod/chromedp session. +func StartRod() (*RodSession, error) { + return StartRodWithProxy("") } -// Stop stop the selenium session. -func (wds *WebDriverSession) Stop() error { - var coverage map[string]interface{} - - coverageDir := "../../web/.nyc_output" - time := time.Now() - - resp, err := wds.WebDriver.ExecuteScriptRaw("return JSON.stringify(window.__coverage__)", nil) +// Stop stop the rod/chromedp session. +func (rs *RodSession) Stop() error { + err := rs.WebDriver.Close() if err != nil { return err } - err = json.Unmarshal(resp, &coverage) - if err != nil { - return err - } + rs.Launcher.Cleanup() - coverageData := fmt.Sprintf("%s", coverage["value"]) - - _ = os.MkdirAll(coverageDir, 0775) - - err = ioutil.WriteFile(fmt.Sprintf("%s/coverage-%d.json", coverageDir, time.Unix()), []byte(coverageData), 0664) //nolint:gosec - if err != nil { - return err - } - - err = filepath.Walk("../../web/.nyc_output", fixCoveragePath) - if err != nil { - return err - } - - err = wds.WebDriver.Quit() - if err != nil { - return err - } - - return wds.service.Stop() -} - -// WithWebdriver run some actions against a webdriver. -func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error { - wds, err := StartWebDriver() - - if err != nil { - return err - } - - defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. - - return fn(wds.WebDriver) -} - -// Wait wait until condition holds true. -func (wds *WebDriverSession) Wait(ctx context.Context, condition selenium.Condition) error { - done := make(chan error, 1) - - go func() { - done <- wds.WebDriver.Wait(condition) - }() - - select { - case <-ctx.Done(): - return errors.New("waiting timeout reached") - case err := <-done: - return err - } -} - -func (wds *WebDriverSession) waitElementLocated(ctx context.Context, t *testing.T, by, value string) selenium.WebElement { - var el selenium.WebElement - - err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { - var err error - el, err = driver.FindElement(by, value) - - if err != nil { - if strings.Contains(err.Error(), "no such element") { - return false, nil - } - return false, err - } - - return el != nil, nil - }) - - require.NoError(t, err) - require.NotNil(t, el) - - return el -} - -func (wds *WebDriverSession) waitElementsLocated(ctx context.Context, t *testing.T, by, value string) []selenium.WebElement { - var el []selenium.WebElement - - err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { - var err error - el, err = driver.FindElements(by, value) - - if err != nil { - if strings.Contains(err.Error(), "no such element") { - return false, nil - } - return false, err - } - - return el != nil, nil - }) - - require.NoError(t, err) - require.NotNil(t, el) - - return el -} - -// WaitElementLocatedByID wait an element is located by id. -func (wds *WebDriverSession) WaitElementLocatedByID(ctx context.Context, t *testing.T, id string) selenium.WebElement { - return wds.waitElementLocated(ctx, t, selenium.ByID, id) -} - -// WaitElementLocatedByTagName wait an element is located by tag name. -func (wds *WebDriverSession) WaitElementLocatedByTagName(ctx context.Context, t *testing.T, tagName string) selenium.WebElement { - return wds.waitElementLocated(ctx, t, selenium.ByTagName, tagName) + return err } // WaitElementLocatedByClassName wait an element is located by class name. -func (wds *WebDriverSession) WaitElementLocatedByClassName(ctx context.Context, t *testing.T, className string) selenium.WebElement { - return wds.waitElementLocated(ctx, t, selenium.ByClassName, className) -} +func (rs *RodSession) WaitElementLocatedByClassName(t *testing.T, page *rod.Page, className string) *rod.Element { + e, err := page.Element("." + className) + require.NoError(t, err) + require.NotNil(t, e) -// WaitElementLocatedByLinkText wait an element is located by link text. -func (wds *WebDriverSession) WaitElementLocatedByLinkText(ctx context.Context, t *testing.T, linkText string) selenium.WebElement { - return wds.waitElementLocated(ctx, t, selenium.ByLinkText, linkText) + return e } // WaitElementLocatedByCSSSelector wait an element is located by class name. -func (wds *WebDriverSession) WaitElementLocatedByCSSSelector(ctx context.Context, t *testing.T, cssSelector string) selenium.WebElement { - return wds.waitElementLocated(ctx, t, selenium.ByCSSSelector, cssSelector) +func (rs *RodSession) WaitElementLocatedByCSSSelector(t *testing.T, page *rod.Page, cssSelector string) *rod.Element { + e, err := page.Element("#" + cssSelector) + require.NoError(t, err) + require.NotNil(t, e) + + return e } // WaitElementsLocatedByCSSSelector wait an element is located by CSS selector. -func (wds *WebDriverSession) WaitElementsLocatedByCSSSelector(ctx context.Context, t *testing.T, cssSelector string) []selenium.WebElement { - return wds.waitElementsLocated(ctx, t, selenium.ByCSSSelector, cssSelector) +func (rs *RodSession) WaitElementsLocatedByCSSSelector(t *testing.T, page *rod.Page, cssSelector string) rod.Elements { + e, err := page.Elements("#" + cssSelector) + require.NoError(t, err) + require.NotNil(t, e) + + return e } -// WaitElementTextContains wait the text of an element contains a pattern. -func (wds *WebDriverSession) WaitElementTextContains(ctx context.Context, t *testing.T, element selenium.WebElement, pattern string) { - err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { - text, err := element.Text() +func (rs *RodSession) waitBodyContains(t *testing.T, page *rod.Page, pattern string) { + text, err := page.MustElementR("body", pattern).Text() + require.NoError(t, err) + require.NotNil(t, text) - if err != nil { - return false, err - } + if strings.Contains(text, pattern) { + err = nil + } else { + err = fmt.Errorf("body does not contain pattern: %s", pattern) + } - return strings.Contains(text, pattern), nil - }) - require.NoError(t, err) -} - -func (wds *WebDriverSession) waitBodyContains(ctx context.Context, t *testing.T, pattern string) { - err := wds.Wait(ctx, func(driver selenium.WebDriver) (bool, error) { - text, err := wds.WaitElementLocatedByTagName(ctx, t, "body").Text() - - if err != nil { - return false, err - } - - return strings.Contains(text, pattern), nil - }) require.NoError(t, err) } diff --git a/internal/utils/const.go b/internal/utils/const.go index 21c1619b..73ab546f 100644 --- a/internal/utils/const.go +++ b/internal/utils/const.go @@ -33,7 +33,7 @@ const ( const ( // Hour is an int based representation of the time unit. - Hour = time.Minute * 60 + Hour = time.Minute * 60 //nolint: revive // Day is an int based representation of the time unit. Day = Hour * 24