diff --git a/test/integration/footer-links.test.js b/test/integration/footer-links.test.js
index cc8aefc4f..d55417f2d 100644
--- a/test/integration/footer-links.test.js
+++ b/test/integration/footer-links.test.js
@@ -5,7 +5,8 @@ const SeleniumHelper = require('./selenium-helpers.js');
 const {
     clickText,
     buildDriver,
-    findText
+    findText,
+    waitUntilDocumentReady
 } = new SeleniumHelper();
 
 const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
@@ -28,14 +29,9 @@ describe('www-integration footer links', () => {
 
     // ==== About Scratch column ====
 
-    const pageLoadComplete = () =>
-        driver.wait(async () => {
-            return await driver.executeScript('return document.readyState;') === 'complete';
-        });
-
     test('click About Scratch link', async () => {
         await clickText('About Scratch');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/about\/?$/);
@@ -43,7 +39,7 @@ describe('www-integration footer links', () => {
 
     test('click For Parents link', async () => {
         await clickText('For Parents');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/parents\/?$/);
@@ -51,7 +47,7 @@ describe('www-integration footer links', () => {
 
     test('click For Educators link', async () => {
         await clickText('For Educators');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/educators\/?$/);
@@ -59,7 +55,7 @@ describe('www-integration footer links', () => {
 
     test('click For Developers link', async () => {
         await clickText('For Developers');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/developers\/?$/);
@@ -69,7 +65,7 @@ describe('www-integration footer links', () => {
 
     test('click Community Guidelines link', async () => {
         await clickText('Community Guidelines');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/community_guidelines\/?$/);
@@ -77,7 +73,7 @@ describe('www-integration footer links', () => {
 
     test('click Discussion Forums link', async () => {
         await clickText('Discussion Forums');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/discuss\/?$/);
@@ -85,7 +81,7 @@ describe('www-integration footer links', () => {
 
     test('click Statistics link', async () => {
         await clickText('Statistics');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/statistics\/?$/);
@@ -95,7 +91,7 @@ describe('www-integration footer links', () => {
 
     test('click Ideas link', async () => {
         await clickText('Ideas');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/ideas\/?$/);
@@ -103,7 +99,7 @@ describe('www-integration footer links', () => {
 
     test('click FAQ link', async () => {
         await clickText('FAQ');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/faq\/?$/);
@@ -111,7 +107,7 @@ describe('www-integration footer links', () => {
 
     test('click Download link', async () => {
         await clickText('Download');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/download\/?$/);
@@ -119,7 +115,7 @@ describe('www-integration footer links', () => {
 
     test('click Contact Us link', async () => {
         await clickText('Contact Us');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/contact-us\/?$/);
@@ -129,7 +125,7 @@ describe('www-integration footer links', () => {
 
     test('click Terms of Use link', async () => {
         await clickText('Terms of Use');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/terms_of_use\/?$/);
@@ -137,7 +133,7 @@ describe('www-integration footer links', () => {
 
     test('click Privacy Policy link', async () => {
         await clickText('Privacy Policy');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/privacy_policy\/?$/);
@@ -145,7 +141,7 @@ describe('www-integration footer links', () => {
 
     test('click Cookies link', async () => {
         await clickText('Cookies');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/cookies\/?$/);
@@ -158,7 +154,7 @@ describe('www-integration footer links', () => {
 
     test('click DMCA link', async () => {
         await clickText('DMCA');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/DMCA\/?$/);
@@ -168,7 +164,7 @@ describe('www-integration footer links', () => {
 
     test('click Scratch Conference link', async () => {
         await clickText('Scratch Conference');
-        await pageLoadComplete();
+        await waitUntilDocumentReady();
         const url = await driver.getCurrentUrl();
         const pathname = (new URL(url)).pathname;
         expect(pathname).toMatch(/^\/scratch-conference\/?$/);
diff --git a/test/integration/join.test.js b/test/integration/join.test.js
index 9c3d58e73..83f8e56eb 100644
--- a/test/integration/join.test.js
+++ b/test/integration/join.test.js
@@ -3,9 +3,11 @@
 const SeleniumHelper = require('./selenium-helpers.js');
 
 const {
-    findByXpath,
+    buildDriver,
     clickXpath,
-    buildDriver
+    findByXpath,
+    navigate,
+    waitUntilDocumentReady
 } = new SeleniumHelper();
 
 const rootUrl = process.env.ROOT_URL || 'https://scratch.ly';
@@ -18,14 +20,14 @@ let driver;
 describe('www-integration join flow', () => {
     beforeAll(async () => {
         driver = await buildDriver('www-integration join flow');
-        await driver.get(rootUrl);
     });
 
     afterAll(() => driver.quit());
 
     beforeEach(async () => {
-        await driver.get(rootUrl);
-        await clickXpath('//a[@class="registrationLink"]');
+        await navigate(rootUrl); // navigate to home page
+        await clickXpath('//a[@class="registrationLink"]'); // navigate to join page
+        await waitUntilDocumentReady();
     });
 
     test('click Join opens join modal', async () => {
@@ -35,22 +37,24 @@ describe('www-integration join flow', () => {
     });
 
     test('username validation message appears', async () => {
-        await clickXpath('//input[contains(@name, "username")]');
+        const clickedInput = await clickXpath('//input[contains(@name, "username")]');
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
         const message = await findByXpath('//div[contains(@class, "validation-message")]');
         const messageText = await message.getText();
         expect(messageText).toEqual('Don\'t use your real name');
-
     });
 
     test('password validation message appears', async () => {
-        await clickXpath('//input[contains(@name, "password")]');
+        const clickedInput = await clickXpath('//input[contains(@name, "password")]');
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
         const message = await findByXpath('//div[contains(@class, "validation-message")]');
         const messageText = await message.getText();
         expect(messageText).toContain('Write it down so you remember.');
     });
 
     test('password confirmation validation message appears', async () => {
-        await clickXpath('//input[contains(@name, "passwordConfirm")]');
+        const clickedInput = await clickXpath('//input[contains(@name, "passwordConfirm")]');
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', clickedInput));
         const message = await findByXpath('//div[contains(@class, "validation-message")]');
         const messageText = await message.getText();
         expect(messageText).toEqual('Type password again');
@@ -59,6 +63,7 @@ describe('www-integration join flow', () => {
     test('username validation: too short', async () => {
         const textInput = await findByXpath('//input[contains(@name, "username")]');
         await textInput.click();
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
         await textInput.sendKeys('ab');
         await clickXpath('//div[@class = "join-flow-outer-content"]');
         const message = await findByXpath('//div[contains(@class, "validation-error")]');
@@ -69,6 +74,7 @@ describe('www-integration join flow', () => {
     test('username validation: username taken', async () => {
         const textInput = await findByXpath('//input[contains(@name, "username")]');
         await textInput.click();
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
         await textInput.sendKeys(takenUsername);
         await clickXpath('//div[@class = "join-flow-outer-content"]');
         const message = await findByXpath('//div[contains(@class, "validation-error")]');
@@ -79,6 +85,7 @@ describe('www-integration join flow', () => {
     test('username validation: bad word', async () => {
         const textInput = await findByXpath('//input[contains(@name, "username")]');
         await textInput.click();
+        await driver.wait(() => driver.executeScript('return document.activeElement == arguments[0]', textInput));
         // Should be caught by the filter
         await textInput.sendKeys('xxxxxxxxx');
         await clickXpath('//div[@class = "join-flow-outer-content"]');
diff --git a/test/integration/selenium-helpers.js b/test/integration/selenium-helpers.js
index 0a8638e6d..888ce270b 100644
--- a/test/integration/selenium-helpers.js
+++ b/test/integration/selenium-helpers.js
@@ -136,8 +136,10 @@ class SeleniumHelper {
             'getLogs',
             'getSauceDriver',
             'isSignedIn',
+            'navigate',
             'signIn',
             'urlMatches',
+            'waitUntilDocumentReady',
             'waitUntilGone'
         ]);
 
@@ -248,6 +250,36 @@ class SeleniumHelper {
         return Key[keyName];
     }
 
+    /**
+     * Wait until the document is ready (i.e. the document.readyState is 'complete')
+     * @returns {Promise} A promise that resolves when the document is ready
+     */
+    async waitUntilDocumentReady () {
+        const outerError = new SeleniumHelperError('waitUntilDocumentReady failed');
+        try {
+            await this.driver.wait(async () =>
+                await this.driver.executeScript('return document.readyState;') === 'complete'
+            );
+        } catch (cause) {
+            throw await outerError.chain(cause, this.driver);
+        }
+    }
+
+    /**
+     * Navigate to the given URL and wait until the document is ready
+     * @param {string} url The URL to navigate to.
+     * @returns {Promise} A promise that resolves when the document is ready
+     */
+    async navigate (url) {
+        const outerError = new SeleniumHelperError('navigate failed', [{url}]);
+        try {
+            await this.driver.get(url);
+            await this.waitUntilDocumentReady();
+        } catch (cause) {
+            throw await outerError.chain(cause, this.driver);
+        }
+    }
+
     /**
      * Find an element by xpath.
      * @param {string} xpath The xpath to search for.
@@ -287,29 +319,33 @@ class SeleniumHelper {
         return this.driver.wait(async () => {
             const elementAtPath = await this.findByXpath(xpath);
             if (!elementAtPath) {
-                return;
+                return null;
             }
 
             if (allowScrolling) {
-                await this.driver.executeScript(
+                const info = await this.driver.executeScript(
                     `
+                    const info = {};
                     const element = arguments[0];
                     const boundingRect = element.getBoundingClientRect();
-                    boundingRect.windowWidth = window.innerWidth;
-                    boundingRect.windowHeight = window.innerHeight;
                     if (boundingRect.top < 0 || boundingRect.bottom > window.innerHeight ||
                         boundingRect.left < 0 || boundingRect.right > window.innerWidth)
                     {
-                        boundingRect.scrollIntoView = true;
                         element.scrollIntoView({
                             behavior: 'instant',
                             block:'nearest',
                             inline: 'nearest'
                         });
+                        info.didScroll = true;
                     }
+                    return info;
                     `,
                     elementAtPath
                 );
+                if (info.didScroll) {
+                    // try again after the scroll completes
+                    return null;
+                }
             }
 
             const elementAtPoint = await this.driver.executeScript(
@@ -323,7 +359,7 @@ class SeleniumHelper {
                 elementAtPath
             );
             if (!elementAtPoint) {
-                return;
+                return null;
             }
             // If we ask to click on a button and Selenium finds an image on the button, or vice versa, that's OK.
             // It doesn't have to be an exact match.
@@ -333,13 +369,13 @@ class SeleniumHelper {
                 elementAtPoint
             );
             if (!match) {
-                return;
+                return null;
             }
             if (!await elementAtPath.isDisplayed()) {
-                return;
+                return null;
             }
             if (!await elementAtPath.isEnabled()) {
-                return;
+                return null;
             }
             return elementAtPath;
         });
@@ -348,13 +384,15 @@ class SeleniumHelper {
     /**
      * Wait until an element can be found by the provided xpath, then click on it.
      * @param {string} xpath The xpath to click.
+     * @param {boolean} [allowScrolling] Whether or not to allow page scrolling to reach the element.
      * @returns {Promise} A promise that resolves when the element is clicked.
      */
-    async clickXpath (xpath) {
+    async clickXpath (xpath, allowScrolling = true) {
         const outerError = new SeleniumHelperError('clickXpath failed', [{xpath}]);
         try {
-            const element = await this.waitUntilClickable(xpath);
+            const element = await this.waitUntilClickable(xpath, allowScrolling);
             element.click();
+            return element;
         } catch (cause) {
             throw await outerError.chain(cause, this.driver);
         }