Speedup and simplify tests

This drastically speeds up tests and simplifies test writing by making
it so that no explicit calls for waiting are needed. Elements that are
queried for now implicitly waits for them to be available.

On my machine, tests used to take 3-4 minutes to complete. Now they take
~1 minute to complete.
This commit is contained in:
Joey Yakimowich-Payne 2020-02-16 19:19:21 -07:00 • committed by Andreas Rumpf
commit 5a4f44b4ee
7 changed files with 40 additions and 83 deletions

View file

@ -55,7 +55,7 @@ task blankdb, "Creates a blank DB":
task test, "Runs tester": task test, "Runs tester":
exec "nimble c -y src/forum.nim" exec "nimble c -y src/forum.nim"
exec "nimble c -y -r tests/browsertester" exec "nimble c -y -r -d:actionDelayMs=0 tests/browsertester"
task fasttest, "Runs tester without recompiling backend": task fasttest, "Runs tester without recompiling backend":
exec "nimble c -r tests/browsertester" exec "nimble c -r -d:actionDelayMs=0 tests/browsertester"

View file

@ -149,4 +149,4 @@ proc render(): VNode =
]) ])
window.onPopState = onPopState window.onPopState = onPopState
setRenderer render setRenderer render

View file

@ -18,25 +18,21 @@ proc categoriesUserTests(session: Session, baseUrl: string) =
with session: with session:
navigate baseUrl navigate baseUrl
wait()
login "user", "user" login "user", "user"
setup: setup:
with session: with session:
navigate baseUrl navigate baseUrl
wait()
test "no category add available": test "no category add available":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
checkIsNone "#add-category" checkIsNone "#add-category"
test "can create category thread": test "can create category thread":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
sendKeys "#thread-title", title sendKeys "#thread-title", title
@ -45,12 +41,10 @@ proc categoriesUserTests(session: Session, baseUrl: string) =
sendKeys "#reply-textarea", content sendKeys "#reply-textarea", content
click "#create-thread-btn" click "#create-thread-btn"
wait()
checkText "#thread-title .category", "Fun" checkText "#thread-title .category", "Fun"
navigate baseUrl navigate baseUrl
wait()
ensureExists title, LinkTextSelector ensureExists title, LinkTextSelector
@ -65,18 +59,15 @@ proc categoriesAdminTests(session: Session, baseUrl: string) =
suite "admin tests": suite "admin tests":
with session: with session:
navigate baseUrl navigate baseUrl
wait()
login "admin", "admin" login "admin", "admin"
test "can create category": test "can create category":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
ensureExists "#add-category" ensureExists "#add-category"
click "#add-category .plus-btn" click "#add-category .plus-btn"
wait()
clear "#add-category input[name='name']" clear "#add-category input[name='name']"
clear "#add-category input[name='color']" clear "#add-category input[name='color']"
@ -88,7 +79,6 @@ proc categoriesAdminTests(session: Session, baseUrl: string) =
sendKeys "#add-category input[name='description']", description sendKeys "#add-category input[name='description']", description
click "#add-category #add-category-btn" click "#add-category #add-category-btn"
wait()
checkText "#category-selection .selected-category", name checkText "#category-selection .selected-category", name
@ -96,10 +86,8 @@ proc categoriesAdminTests(session: Session, baseUrl: string) =
proc test*(session: Session, baseUrl: string) = proc test*(session: Session, baseUrl: string) =
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()
categoriesUserTests(session, baseUrl) categoriesUserTests(session, baseUrl)
categoriesAdminTests(session, baseUrl) categoriesAdminTests(session, baseUrl)
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()

View file

@ -2,6 +2,9 @@ import os, options, unittest, strutils
import webdriver import webdriver
import macros import macros
const actionDelayMs {.intdefine.} = 0
## Inserts a delay in milliseconds between automated actions. Useful for debugging tests
macro with*(obj: typed, code: untyped): untyped = macro with*(obj: typed, code: untyped): untyped =
## Execute a set of statements with an object ## Execute a set of statements with an object
expectKind code, nnkStmtList expectKind code, nnkStmtList
@ -12,24 +15,28 @@ macro with*(obj: typed, code: untyped): untyped =
if result[i].kind in {nnkCommand, nnkCall}: if result[i].kind in {nnkCommand, nnkCall}:
result[i].insert(1, obj) result[i].insert(1, obj)
proc elementIsSome(element: Option[Element]): bool =
return element.isSome
proc elementIsNone(element: Option[Element]): bool =
return element.isNone
proc waitForElement*(session: Session, selector: string, strategy=CssSelector, timeout=20000, pollTime=50, waitCondition=elementIsSome): Option[Element]
template click*(session: Session, element: string, strategy=CssSelector) = template click*(session: Session, element: string, strategy=CssSelector) =
let el = session.findElement(element, strategy) let el = session.waitForElement(element, strategy)
check el.isSome()
el.get().click() el.get().click()
template sendKeys*(session: Session, element, keys: string) = template sendKeys*(session: Session, element, keys: string) =
let el = session.findElement(element) let el = session.waitForElement(element)
check el.isSome()
el.get().sendKeys(keys) el.get().sendKeys(keys)
template clear*(session: Session, element: string) = template clear*(session: Session, element: string) =
let el = session.findElement(element) let el = session.waitForElement(element)
check el.isSome()
el.get().clear() el.get().clear()
template sendKeys*(session: Session, element: string, keys: varargs[Key]) = template sendKeys*(session: Session, element: string, keys: varargs[Key]) =
let el = session.findElement(element) let el = session.waitForElement(element)
check el.isSome()
# focus # focus
el.get().click() el.get().click()
@ -37,47 +44,47 @@ template sendKeys*(session: Session, element: string, keys: varargs[Key]) =
session.press(key) session.press(key)
template ensureExists*(session: Session, element: string, strategy=CssSelector) = template ensureExists*(session: Session, element: string, strategy=CssSelector) =
let el = session.findElement(element, strategy) discard session.waitForElement(element, strategy)
check el.isSome()
template check*(session: Session, element: string, function: untyped) = template check*(session: Session, element: string, function: untyped) =
let el = session.findElement(element) let el = session.waitForElement(element)
check function(el) check function(el)
template check*(session: Session, element: string, template check*(session: Session, element: string,
strategy: LocationStrategy, function: untyped) = strategy: LocationStrategy, function: untyped) =
let el = session.findElement(element, strategy) let el = session.waitForElement(element, strategy)
check function(el) check function(el)
template checkIsNone*(session: Session, element: string, strategy=CssSelector) = template checkIsNone*(session: Session, element: string, strategy=CssSelector) =
let el = session.findElement(element, strategy) discard session.waitForElement(element, strategy, waitCondition=elementIsNone)
check el.isNone()
template checkText*(session: Session, element, expectedValue: string) = template checkText*(session: Session, element, expectedValue: string) =
let el = session.findElement(element) let el = session.waitForElement(element)
check el.isSome()
check el.get().getText() == expectedValue check el.get().getText() == expectedValue
proc waitForLoad*(session: Session, timeout=20000) = proc waitForElement*(
session: Session, selector: string, strategy=CssSelector,
timeout=20000, pollTime=50,
waitCondition=elementIsSome
): Option[Element] =
var waitTime = 0 var waitTime = 0
sleep(2000)
when actionDelayMs > 0:
sleep(actionDelayMs)
while true: while true:
let loading = session.findElement(".loading") let loading = session.findElement(selector, strategy)
if loading.isNone: return if waitCondition(loading):
sleep(1000) return loading
waitTime += 1000 sleep(pollTime)
waitTime += pollTime
if waitTime > timeout: if waitTime > timeout:
doAssert false, "Wait for load time exceeded" doAssert false, "Wait for load time exceeded"
proc wait*(session: Session, msTimeout: int = 5000) =
session.waitForLoad(msTimeout)
proc setUserRank*(session: Session, baseUrl, user, rank: string) = proc setUserRank*(session: Session, baseUrl, user, rank: string) =
with session: with session:
navigate(baseUrl & "profile/" & user) navigate(baseUrl & "profile/" & user)
wait()
click "#settings-tab" click "#settings-tab"
@ -85,13 +92,11 @@ proc setUserRank*(session: Session, baseUrl, user, rank: string) =
click("#rank-field option#rank-" & rank.toLowerAscii) click("#rank-field option#rank-" & rank.toLowerAscii)
click "#save-btn" click "#save-btn"
wait()
proc logout*(session: Session) = proc logout*(session: Session) =
with session: with session:
click "#profile-btn" click "#profile-btn"
click "#profile-btn #logout-btn" click "#profile-btn #logout-btn"
wait()
# Verify we have logged out by looking for the log in button. # Verify we have logged out by looking for the log in button.
ensureExists "#login-btn" ensureExists "#login-btn"
@ -108,8 +113,6 @@ proc login*(session: Session, user, password: string) =
sendKeys "#login-form input[name='password']", Key.Enter sendKeys "#login-form input[name='password']", Key.Enter
wait()
# Verify that the user menu has been initialised properly. # Verify that the user menu has been initialised properly.
click "#profile-btn" click "#profile-btn"
checkText "#profile-btn #profile-name", user checkText "#profile-btn #profile-name", user
@ -128,7 +131,6 @@ proc register*(session: Session, user, password: string, verify = true) =
sendKeys "#signup-form input[name='password']", password sendKeys "#signup-form input[name='password']", password
click "#signup-modal .create-account-btn" click "#signup-modal .create-account-btn"
wait()
if verify: if verify:
with session: with session:
@ -141,13 +143,11 @@ proc register*(session: Session, user, password: string, verify = true) =
proc createThread*(session: Session, title, content: string) = proc createThread*(session: Session, title, content: string) =
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
sendKeys "#thread-title", title sendKeys "#thread-title", title
sendKeys "#reply-textarea", content sendKeys "#reply-textarea", content
click "#create-thread-btn" click "#create-thread-btn"
wait()
checkText "#thread-title .title-text", title checkText "#thread-title .title-text", title
checkText ".original-post div.post-content", content checkText ".original-post div.post-content", content

View file

@ -5,8 +5,6 @@ import webdriver
proc test*(session: Session, baseUrl: string) = proc test*(session: Session, baseUrl: string) =
session.navigate(baseUrl) session.navigate(baseUrl)
waitForLoad(session)
test "can see banned posts": test "can see banned posts":
with session: with session:
register("issue181", "issue181") register("issue181", "issue181")
@ -19,7 +17,6 @@ proc test*(session: Session, baseUrl: string) =
login("issue181", "issue181") login("issue181", "issue181")
navigate(baseUrl) navigate(baseUrl)
wait()
const title = "Testing issue 181." const title = "Testing issue 181."
createThread(title, "Test for issue #181") createThread(title, "Test for issue #181")
@ -33,7 +30,6 @@ proc test*(session: Session, baseUrl: string) =
# Make sure the banned user's thread is still visible. # Make sure the banned user's thread is still visible.
navigate(baseUrl) navigate(baseUrl)
wait()
ensureExists("tr.banned") ensureExists("tr.banned")
checkText("tr.banned .thread-title > a", title) checkText("tr.banned .thread-title > a", title)
logout() logout()

View file

@ -5,8 +5,6 @@ import webdriver
proc test*(session: Session, baseUrl: string) = proc test*(session: Session, baseUrl: string) =
session.navigate(baseUrl) session.navigate(baseUrl)
waitForLoad(session)
# Sanity checks # Sanity checks
test "shows sign up": test "shows sign up":
session.checkText("#signup-btn", "Sign up") session.checkText("#signup-btn", "Sign up")
@ -38,10 +36,8 @@ proc test*(session: Session, baseUrl: string) =
logout() logout()
navigate baseUrl navigate baseUrl
wait()
register "TEst1", "test1", verify = false register "TEst1", "test1", verify = false
ensureExists "#signup-form .has-error" ensureExists "#signup-form .has-error"
navigate baseUrl navigate baseUrl
wait()

View file

@ -1,4 +1,4 @@
import unittest, options, os, common import unittest, options, common
import webdriver import webdriver
@ -27,18 +27,15 @@ proc userTests(session: Session, baseUrl: string) =
setup: setup:
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()
test "can create thread": test "can create thread":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
sendKeys "#thread-title", userTitleStr sendKeys "#thread-title", userTitleStr
sendKeys "#reply-textarea", userContentStr sendKeys "#reply-textarea", userContentStr
click "#create-thread-btn" click "#create-thread-btn"
wait()
checkText "#thread-title .title-text", userTitleStr checkText "#thread-title .title-text", userTitleStr
checkText ".original-post div.post-content", userContentStr checkText ".original-post div.post-content", userContentStr
@ -50,7 +47,6 @@ proc anonymousTests(session: Session, baseUrl: string) =
suite "anonymous user tests": suite "anonymous user tests":
with session: with session:
navigate baseUrl navigate baseUrl
wait()
test "can view banned thread": test "can view banned thread":
with session: with session:
@ -58,25 +54,21 @@ proc anonymousTests(session: Session, baseUrl: string) =
with session: with session:
navigate baseUrl navigate baseUrl
wait()
proc bannedTests(session: Session, baseUrl: string) = proc bannedTests(session: Session, baseUrl: string) =
suite "banned user thread tests": suite "banned user thread tests":
with session: with session:
navigate baseUrl navigate baseUrl
wait()
login "banned", "banned" login "banned", "banned"
test "can't start thread": test "can't start thread":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
sendKeys "#thread-title", "test" sendKeys "#thread-title", "test"
sendKeys "#reply-textarea", "test" sendKeys "#reply-textarea", "test"
click "#create-thread-btn" click "#create-thread-btn"
wait()
ensureExists "#new-thread p.text-error" ensureExists "#new-thread p.text-error"
@ -88,7 +80,6 @@ proc adminTests(session: Session, baseUrl: string) =
setup: setup:
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()
test "can view banned thread": test "can view banned thread":
with session: with session:
@ -97,13 +88,11 @@ proc adminTests(session: Session, baseUrl: string) =
test "can create thread": test "can create thread":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
sendKeys "#thread-title", adminTitleStr sendKeys "#thread-title", adminTitleStr
sendKeys "#reply-textarea", adminContentStr sendKeys "#reply-textarea", adminContentStr
click "#create-thread-btn" click "#create-thread-btn"
wait()
checkText "#thread-title .title-text", adminTitleStr checkText "#thread-title .title-text", adminTitleStr
checkText ".original-post div.post-content", adminContentStr checkText ".original-post div.post-content", adminContentStr
@ -111,7 +100,6 @@ proc adminTests(session: Session, baseUrl: string) =
test "try create duplicate thread": test "try create duplicate thread":
with session: with session:
click "#new-thread-btn" click "#new-thread-btn"
wait()
ensureExists "#new-thread" ensureExists "#new-thread"
sendKeys "#thread-title", adminTitleStr sendKeys "#thread-title", adminTitleStr
@ -119,22 +107,17 @@ proc adminTests(session: Session, baseUrl: string) =
click "#create-thread-btn" click "#create-thread-btn"
wait()
ensureExists "#new-thread p.text-error" ensureExists "#new-thread p.text-error"
test "can edit post": test "can edit post":
let modificationText = " and I edited it!" let modificationText = " and I edited it!"
with session: with session:
click adminTitleStr, LinkTextSelector click adminTitleStr, LinkTextSelector
wait()
click ".post-buttons .edit-button" click ".post-buttons .edit-button"
wait()
sendKeys ".original-post #reply-textarea", modificationText sendKeys ".original-post #reply-textarea", modificationText
click ".edit-buttons .save-button" click ".edit-buttons .save-button"
wait()
checkText ".original-post div.post-content", adminContentStr & modificationText checkText ".original-post div.post-content", adminContentStr & modificationText
@ -143,7 +126,6 @@ proc adminTests(session: Session, baseUrl: string) =
with session: with session:
click userTitleStr, LinkTextSelector click userTitleStr, LinkTextSelector
wait()
click ".post-buttons .like-button" click ".post-buttons .like-button"
@ -152,14 +134,11 @@ proc adminTests(session: Session, baseUrl: string) =
test "can delete thread": test "can delete thread":
with session: with session:
click adminTitleStr, LinkTextSelector click adminTitleStr, LinkTextSelector
wait()
click ".post-buttons .delete-button" click ".post-buttons .delete-button"
wait()
# click delete confirmation # click delete confirmation
click "#delete-modal .delete-btn" click "#delete-modal .delete-btn"
wait()
# Make sure the forum post is gone # Make sure the forum post is gone
checkIsNone adminTitleStr, LinkTextSelector checkIsNone adminTitleStr, LinkTextSelector
@ -168,7 +147,6 @@ proc adminTests(session: Session, baseUrl: string) =
proc test*(session: Session, baseUrl: string) = proc test*(session: Session, baseUrl: string) =
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()
userTests(session, baseUrl) userTests(session, baseUrl)
@ -180,5 +158,4 @@ proc test*(session: Session, baseUrl: string) =
unBanUser(session, baseUrl) unBanUser(session, baseUrl)
session.navigate(baseUrl) session.navigate(baseUrl)
session.wait()