diff options
64 files changed, 1138 insertions, 1026 deletions
@@ -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 @@ -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 55d90fa6b..96f35e7ca 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 64ead3a43..fcb281c16 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 36cac95c8..c8c27afcc 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 728e721d1..c8328b5ff 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 69f566f9b..d0fa38227 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 2461e6a32..95e1a72e4 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 62c473205..5fb36b70b 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 d1300159e..2a252cfd2 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 7be5ae361..68cd2652f 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 bf682ba0f..ed06a4515 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 995aa220b..976fb70d0 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 85f3e041d..497b837e6 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 ea5edcde4..e03c903ec 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) - -	err := s.Wait(ctx, func(d selenium.WebDriver) (bool, error) { -		body, err := s.WebDriver().FindElement(selenium.ByTagName, "body") -		if err != nil { -			return false, err -		} - -		if body == nil { -			return false, nil -		} - -		content, err := body.Text() -		if err != nil { -			return false, err -		} - -		payload := HeadersPayload{} -		if err := json.Unmarshal([]byte(content), &payload); err != nil { -			return false, err -		} - -		groups := strings.Split(payload.Headers.ForwardedGroups, ",") -		actualGroups := mapset.NewSet() -		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 -	}) - -	require.NoError(s.T(), err) +	s.doLoginOneFactor(s.T(), s.Context(ctx), "john", "password", false, targetURL) +	s.verifyIsPublic(s.T(), s.Context(ctx)) + +	body, err := s.Context(ctx).Element("body") +	s.Assert().NoError(err) +	s.Assert().NotNil(body) + +	content, err := body.Text() +	s.Assert().NoError(err) +	s.Assert().NotNil(content) + +	payload := HeadersPayload{} +	if err := json.Unmarshal([]byte(content), &payload); err != nil { +		log.Panic(err) +	} + +	groups := strings.Split(payload.Headers.ForwardedGroups, ",") +	actualGroups := mapset.NewSet() + +	for _, group := range groups { +		actualGroups.Add(group) +	} + +	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") +	} + +	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 b4dd7c65b..6dd521309 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 - -	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) -	defer cancel() - -	targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) -	drus.secret = drus.doRegisterAndLogin2FA(ctx, drus.T(), "john", "password", false, targetURL) -	drus.verifySecretAuthorized(ctx, drus.T()) +	s.RodSession = browser  } -func (drus *DefaultRedirectionURLScenario) TearDownSuite() { -	err := drus.WebDriverSession.Stop() +func (s *DefaultRedirectionURLScenario) TearDownSuite() { +	err := s.RodSession.Stop()  	if err != nil {  		log.Fatal(err)  	}  } -func (drus *DefaultRedirectionURLScenario) SetupTest() { -	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -	defer cancel() +func (s *DefaultRedirectionURLScenario) SetupTest() { +	s.Page = s.doCreateTab(s.T(), HomeBaseURL) +	s.verifyIsHome(s.T(), s.Page) +} -	drus.doLogout(ctx, drus.T()) -	drus.doVisit(drus.T(), HomeBaseURL) -	drus.verifyIsHome(ctx, drus.T()) +func (s *DefaultRedirectionURLScenario) TearDownTest() { +	s.collectCoverage(s.Page) +	s.MustClose()  } -func (drus *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() { -	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) -	defer cancel() +func (s *DefaultRedirectionURLScenario) TestUserIsRedirectedToDefaultURL() { +	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) +	defer func() { +		cancel() +		s.collectScreenshot(ctx.Err(), s.Page) +	}() + +	targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL) + +	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)) -	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 83cbdc14e..778b183cd 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 d531a2dab..f97ce0e14 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.collectCoverage(s.Page) +		s.MustClose() +	}() -	s.secret = s.doRegisterAndLogin2FA(ctx, s.T(), "john", "password", false, AdminBaseURL) +	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.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)) +} -	s.doVisit(s.T(), fmt.Sprintf("%s/logout", OIDCBaseURL)) -	s.doLogout(ctx, s.T()) -	s.doVisit(s.T(), HomeBaseURL) -	s.verifyIsHome(ctx, s.T()) +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 febfae7b2..312d66578 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 493f6293a..78e44d0c4 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 af5f8b2e6..4d0b7c4f6 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 5224faa20..6ec35c7d7 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() +func (s *RedirectionURLScenario) SetupTest() { +	s.Page = s.doCreateTab(s.T(), HomeBaseURL) +	s.verifyIsHome(s.T(), s.Page) +} -	rus.doLogout(ctx, rus.T()) -	rus.doVisit(rus.T(), HomeBaseURL) -	rus.verifyIsHome(ctx, rus.T()) +func (s *RedirectionURLScenario) TearDownTest() { +	s.collectCoverage(s.Page) +	s.MustClose()  } -func (rus *RedirectionURLScenario) TestShouldVerifyCustomURLParametersArePropagatedAfterRedirection() { +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 a350b8593..272e1a526 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 203546611..755deb7a6 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 9b1ea897f..09e9eb647 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 9ff5c6e24..79288467b 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 b9620dd8f..4d51509f2 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 c99bf479b..48e7f9b0a 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 c6ae052b5..4450d6206 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 730735f9b..8b6f23b7d 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 3a7eaf255..05dcbf788 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.collectCoverage(s.Page) +		s.MustClose() +	}() -	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.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() - -	s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") -	s.doChangeMethod(ctx, s.T(), "push-notification") -	s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") +	defer func() { +		cancel() +		s.collectScreenshot(ctx.Err(), s.Page) +	}() + +	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 a571a47f8..160d1b9ef 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 276214a3e..1c66242b2 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 ce5f69136..b18bcfa68 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 8cb61c71b..a40889dd7 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 5859d936f..96334f8a3 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 cae16a501..eb201aa6b 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 e6e6c6f7f..1a634ad27 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 a8562a755..82b1e90d2 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 4c6326e0f..acf224dd8 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 38b8292ab..eae84c4d6 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 4b1971fd2..1588245e3 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() - -	s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "") -	s.verifyURLIs(ctx, s.T(), HomeBaseURL+"/") -	s.doVisit(s.T(), GetLoginBaseURL()) -	s.verifyIsAuthenticatedPage(ctx, s.T()) +	defer func() { +		cancel() +		s.collectScreenshot(ctx.Err(), s.Page) +	}() + +	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 e2dd8a534..098897afe 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 8ee97c2f4..616702f54 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 49df7ccaa..f59ebdce8 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 0049b8c56..6f2ce907c 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 0f7e36d75..0cbddf110 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 + +	s.Page = s.doCreateTab(s.T(), HomeBaseURL) +	s.verifyIsHome(s.T(), s.Page) +	secret := s.doRegisterThenLogout(s.T(), s.Context(ctx), "john", "password") -	wds.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "") +	s.doLoginTwoFactor(s.T(), s.Context(ctx), "john", "password", false, secret, "") -	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))  	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 58147674e..3c1fbc7a5 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 c8f4c43a9..4595cf524 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 6e26e87b8..d29e661db 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 != "<nil>" { +		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 011e6366b..8594c2bb5 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 abe01df76..5cb24d4b9 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 d737cfe10..de143a831 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 3afe409c8..208194e30 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 c1bdedf01..a3fa2701e 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 000000000..9e8e22197 --- /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 000000000..2a428d50f --- /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 8cc5c3259..a37ffc27e 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 663594b8c..95cc046e7 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 26dba4792..262dfae13 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 4859e128c..2c6c94f47 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 db3848b06..10c59c62f 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 106207670..e93b23531 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()) -} - -// 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) -	if err != nil { -		return err -	} - -	err = json.Unmarshal(resp, &coverage) -	if err != nil { -		return err -	} - -	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() +// StartRod create a rod/chromedp session. +func StartRod() (*RodSession, error) { +	return StartRodWithProxy("")  } -// WithWebdriver run some actions against a webdriver. -func WithWebdriver(fn func(webdriver selenium.WebDriver) error) error { -	wds, err := StartWebDriver() - +// Stop stop the rod/chromedp session. +func (rs *RodSession) Stop() error { +	err := rs.WebDriver.Close()  	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) +	rs.Launcher.Cleanup() -	go func() { -		done <- wds.WebDriver.Wait(condition) -	}() - -	select { -	case <-ctx.Done(): -		return errors.New("waiting timeout reached") -	case err := <-done: -		return err -	} +	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 -	}) - +// WaitElementLocatedByClassName wait an element is located by class name. +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, el) +	require.NotNil(t, e) -	return el +	return e  } -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 -	}) - +// WaitElementLocatedByCSSSelector wait an element is located by class name. +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, el) +	require.NotNil(t, e) -	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) -} - -// 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) -} - -// 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) -} - -// 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) +	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) -} - -// 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() - -		if err != nil { -			return false, err -		} - -		return strings.Contains(text, pattern), nil -	}) +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  } -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() +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)  } diff --git a/internal/utils/const.go b/internal/utils/const.go index 21c1619b2..73ab546f3 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  | 
