I'm using chromedp to test my Go-based website. While I've managed to do basic login tests with it, I'm getting CSRF errors when I try to sign out of an account that I've just logged in as.
Here's the test function that's getting the CSRF error and its main helper. httpServerURL
is the base URL of either the live webserver running or an httptest.Server.URL
(I get the same CSRF error either way):
func TestSignupDuplicate(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
ctx, cancel = chromedp.NewContext(ctx) // chromedp.WithDebugf(log.Printf),
defer cancel()
email := "[email protected]"
password := "asdfasdf"
signUpWithContext(ctx, t, email, password)
defer func() {
if err := userManager.DeleteByEmail(email); err != nil {
t.Fatal(err)
}
}()
var postSignoutClickLocationGot string
postSignoutClickLocationExpected := httpServerURL + "/"
if err := chromedp.Run(ctx,
chromedp.Click("//button[@class='sign-out-form__button']"),
chromedp.Sleep(800*time.Millisecond),
chromedp.Location(&postSignoutClickLocationGot),
); err != nil {
t.Fatal(err)
}
if postSignoutClickLocationGot != postSignoutClickLocationExpected {
t.Logf("Expected to be redirected to <%s> after signing out, but was here instead: <%s>",
postSignoutClickLocationExpected,
postSignoutClickLocationGot,
)
}
var location string
var html string
if err := chromedp.Run(ctx,
//chromedp.WaitReady("//footer"),
chromedp.Location(&location),
chromedp.InnerHTML("/html", &html),
); err != nil {
t.Fatalf("Had trouble getting debug information: %s", err)
}
log.Println(location)
log.Println(html)
signUpWithContext(ctx, t, email, password)
expectedAlertHeading := "E-mail address already in use"
var gotAlertHeading string
if err := chromedp.Run(ctx,
chromedp.Text("//*[@class='alert__heading']", &gotAlertHeading),
); err != nil {
t.Fatalf("couldn’t get alert heading: %s", err)
}
if expectedAlertHeading != gotAlertHeading {
t.Fatalf("Unexpected alert heading. Want: «%s». Got: «%s»", expectedAlertHeading, gotAlertHeading)
}
}
func signUpWithContext(ctx context.Context, t *testing.T, email, password string) {
t.Helper()
if err := chromedp.Run(ctx,
chromedp.Navigate(httpServerURL+"/signup/"),
chromedp.WaitVisible("#email", chromedp.ByID),
chromedp.SendKeys("#email", email, chromedp.ByID),
chromedp.SendKeys("#password", password, chromedp.ByID),
chromedp.Submit("//button[@type='submit']"),
); err != nil {
t.Fatal(err)
}
}
And here's the output of that:
Running tool: /usr/local/go/bin/go test -timeout 30s example.com/webdictions -run ^(TestSignupDuplicate)$
2019/07/05 15:26:02 http://127.0.0.1:53464/signout/
2019/07/05 15:26:02 <head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">Forbidden - CSRF token invalid
</pre></body>
--- FAIL: TestSignupDuplicate (3.01s)
/Users/comatoast/Projects/predictionsweb/main_test.go:150: Expected to be redirected to <http://127.0.0.1:53464/> after signing out, but was here instead: <http://127.0.0.1:53464/signout/>
/Users/comatoast/Projects/predictionsweb/main_test.go:177: couldn’t get alert heading: context deadline exceeded
FAIL
FAIL example.com/webdictions 3.073s
Oddly enough, a Puppeteer program doesn't error out like this. At its end I don't get any screenshots of CSRF errors, regardless of whether the user already has an account before I start the test:
const puppeteer = require('puppeteer');
(async () => {
const opts = {
width: 800,
height: 600,
deviceScaleFactor: 2,
}
const browser = await puppeteer.launch({defaultViewport: opts});
const page = await browser.newPage();
await page.goto('http://www.localhost:3000/');
await page.click("a[href='/signup/']");
await page.type('#email', "[email protected]");
await page.type('#password', 'asdfasdf');
await page.click('[type="submit"]');
await page.screenshot({path: '1. should be the dashboard after signup.png'});
await page.click('.sign-out-form__button');
await page.screenshot({path: '2. should be slash.png'});
await page.click('a[href="/signup/"]')
await page.screenshot({path: '3. signup again.png'});
await page.type('#email', "[email protected]");
await page.type('#password', 'asdfasdf');
await page.click('[type="submit"]');
await page.screenshot({path: '4. after second identical signup attempt.png'});
// await page.screenshot({path: 'screenshot.png'});
await browser.close();
})();
Likewise, when I try to sign up for the same account twice in either Safari or Chrome, I get a normal "this e-mail address is already in use" error, not a CSRF error. What, if anything, am I doing incorrectly via chromedp?
Turns out, on the second access of the "Sign up" page, I had a one-button "Sign out" form that chromedp.Submit("//button[@type='submit']")
was clicking on. Changing the path to an unambiguous chromedp.Submit("//form[@action='/signup/']//button[@type='submit']")
in signUpWithContext
fixed my problem of clicking on the submit button of the wrong form.