Merge pull request #184 from jyapayne/rework_tests
Make tests nicer to write
This commit is contained in:
commit
93913d9b13
9 changed files with 245 additions and 148 deletions
|
|
@ -94,7 +94,7 @@ when defined(js):
|
||||||
proc render*(state: DeleteModal): VNode =
|
proc render*(state: DeleteModal): VNode =
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv(class=class({"active": state.shown}, "modal modal-sm"),
|
tdiv(class=class({"active": state.shown}, "modal modal-sm"),
|
||||||
id="login-modal"):
|
id="delete-modal"):
|
||||||
a(href="", class="modal-overlay", "aria-label"="close",
|
a(href="", class="modal-overlay", "aria-label"="close",
|
||||||
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
||||||
tdiv(class="modal-container"):
|
tdiv(class="modal-container"):
|
||||||
|
|
@ -122,11 +122,11 @@ when defined(js):
|
||||||
|
|
||||||
button(class=class(
|
button(class=class(
|
||||||
{"loading": state.loading},
|
{"loading": state.loading},
|
||||||
"btn btn-primary"
|
"btn btn-primary delete-btn"
|
||||||
),
|
),
|
||||||
onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)):
|
onClick=(ev: Event, n: VNode) => onDelete(ev, n, state)):
|
||||||
italic(class="fas fa-trash-alt")
|
italic(class="fas fa-trash-alt")
|
||||||
text " Delete"
|
text " Delete"
|
||||||
button(class="btn",
|
button(class="btn cancel-btn",
|
||||||
onClick=(ev: Event, n: VNode) => (state.shown = false)):
|
onClick=(ev: Event, n: VNode) => (state.shown = false)):
|
||||||
text "Cancel"
|
text "Cancel"
|
||||||
|
|
@ -87,7 +87,7 @@ when defined(js):
|
||||||
text state.error.get().message
|
text state.error.get().message
|
||||||
|
|
||||||
tdiv(class="edit-buttons"):
|
tdiv(class="edit-buttons"):
|
||||||
tdiv(class="reply-button"):
|
tdiv(class="cancel-button"):
|
||||||
button(class="btn btn-link",
|
button(class="btn btn-link",
|
||||||
onClick=(e: Event, n: VNode) => (state.onEditCancel())):
|
onClick=(e: Event, n: VNode) => (state.onEditCancel())):
|
||||||
text " Cancel"
|
text " Cancel"
|
||||||
|
|
|
||||||
|
|
@ -78,17 +78,18 @@ when defined(js):
|
||||||
"data-sitekey"=recaptchaSiteKey.get())
|
"data-sitekey"=recaptchaSiteKey.get())
|
||||||
script(src="https://www.google.com/recaptcha/api.js")
|
script(src="https://www.google.com/recaptcha/api.js")
|
||||||
tdiv(class="modal-footer"):
|
tdiv(class="modal-footer"):
|
||||||
button(class=class({"loading": state.loading}, "btn btn-primary"),
|
button(class=class({"loading": state.loading},
|
||||||
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)):
|
"btn btn-primary create-account-btn"),
|
||||||
|
onClick=(ev: Event, n: VNode) => onSignUpClick(ev, n, state)):
|
||||||
text "Create account"
|
text "Create account"
|
||||||
button(class="btn",
|
button(class="btn login-btn",
|
||||||
onClick=(ev: Event, n: VNode) =>
|
onClick=(ev: Event, n: VNode) =>
|
||||||
(state.onLogIn(); state.shown = false)):
|
(state.onLogIn(); state.shown = false)):
|
||||||
text "Log in"
|
text "Log in"
|
||||||
|
|
||||||
p(class="license-text text-gray"):
|
p(class="license-text text-gray"):
|
||||||
text "By registering, you agree to the "
|
text "By registering, you agree to the "
|
||||||
a(href=makeUri("/about/license"),
|
a(id="license", href=makeUri("/about/license"),
|
||||||
onClick=(ev: Event, n: VNode) =>
|
onClick=(ev: Event, n: VNode) =>
|
||||||
(state.shown = false; anchorCB(ev, n))):
|
(state.shown = false; anchorCB(ev, n))):
|
||||||
text "content license"
|
text "content license"
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ when defined(js):
|
||||||
|
|
||||||
proc render*(state: UserMenu, user: User): VNode =
|
proc render*(state: UserMenu, user: User): VNode =
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv():
|
tdiv(id="profile-btn"):
|
||||||
figure(class="avatar c-hand",
|
figure(class="avatar c-hand",
|
||||||
onClick=(e: Event, n: VNode) => onClick(e, n, state)):
|
onClick=(e: Event, n: VNode) => onClick(e, n, state)):
|
||||||
img(src=user.avatarUrl, title=user.name)
|
img(src=user.avatarUrl, title=user.name)
|
||||||
|
|
@ -52,13 +52,15 @@ when defined(js):
|
||||||
tdiv(class="tile-icon"):
|
tdiv(class="tile-icon"):
|
||||||
img(class="avatar", src=user.avatarUrl,
|
img(class="avatar", src=user.avatarUrl,
|
||||||
title=user.name)
|
title=user.name)
|
||||||
tdiv(class="tile-content"):
|
tdiv(id="profile-name", class="tile-content"):
|
||||||
text user.name
|
text user.name
|
||||||
li(class="divider")
|
li(class="divider")
|
||||||
li(class="menu-item"):
|
li(class="menu-item"):
|
||||||
a(href=makeUri("/profile/" & user.name)):
|
a(id="myprofile-btn",
|
||||||
|
href=makeUri("/profile/" & user.name)):
|
||||||
text "My profile"
|
text "My profile"
|
||||||
li(class="menu-item c-hand"):
|
li(class="menu-item c-hand"):
|
||||||
a(onClick = (e: Event, n: VNode) =>
|
a(id="logout-btn",
|
||||||
|
onClick = (e: Event, n: VNode) =>
|
||||||
(state.shown=false; state.onLogout())):
|
(state.shown=false; state.onLogout())):
|
||||||
text "Logout"
|
text "Logout"
|
||||||
|
|
@ -22,10 +22,25 @@ proc backup(path: string, contents: Option[string]=none[string]()) =
|
||||||
echo(path, " already exists. Moving to ", backupPath)
|
echo(path, " already exists. Moving to ", backupPath)
|
||||||
moveFile(path, backupPath)
|
moveFile(path, backupPath)
|
||||||
|
|
||||||
|
proc createUser(db: DbConn, user: tuple[username, password, email: string],
|
||||||
|
rank: Rank) =
|
||||||
|
assert user.username.len != 0
|
||||||
|
let salt = makeSalt()
|
||||||
|
let password = makePassword(user.password, salt)
|
||||||
|
|
||||||
|
exec(db, sql"""
|
||||||
|
INSERT INTO person(name, password, email, salt, status, lastOnline)
|
||||||
|
VALUES (?, ?, ?, ?, ?, DATETIME('now'))
|
||||||
|
""", user.username, password, user.email, salt, $rank)
|
||||||
|
|
||||||
proc initialiseDb(admin: tuple[username, password, email: string],
|
proc initialiseDb(admin: tuple[username, password, email: string],
|
||||||
filename="nimforum.db") =
|
filename="nimforum.db") =
|
||||||
let path = getCurrentDir() / filename
|
let
|
||||||
if "-dev" notin filename and "-test" notin filename:
|
path = getCurrentDir() / filename
|
||||||
|
isTest = "-test" in filename
|
||||||
|
isDev = "-dev" in filename
|
||||||
|
|
||||||
|
if not isDev and not isTest:
|
||||||
backup(path)
|
backup(path)
|
||||||
|
|
||||||
removeFile(path)
|
removeFile(path)
|
||||||
|
|
@ -98,13 +113,16 @@ proc initialiseDb(admin: tuple[username, password, email: string],
|
||||||
db.exec sql"create index PersonStatusIdx on person(status);"
|
db.exec sql"create index PersonStatusIdx on person(status);"
|
||||||
|
|
||||||
# Create default user.
|
# Create default user.
|
||||||
if admin.username.len != 0:
|
db.createUser(admin, Admin)
|
||||||
let salt = makeSalt()
|
|
||||||
let password = makePassword(admin.password, salt)
|
# Create test users if test or development
|
||||||
db.exec(sql"""
|
if isTest or isDev:
|
||||||
insert into person (id, name, password, email, salt, status)
|
for rank in Spammer..Moderator:
|
||||||
values (1, ?, ?, ?, ?, ?);
|
let rankLower = toLowerAscii($rank)
|
||||||
""", admin.username, password, admin.email, salt, $Admin)
|
let user = (username: $rankLower,
|
||||||
|
password: $rankLower,
|
||||||
|
email: $rankLower & "@localhost.local")
|
||||||
|
db.createUser(user, rank)
|
||||||
|
|
||||||
# -- Post
|
# -- Post
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,57 @@
|
||||||
import os, options
|
import os, options, unittest
|
||||||
import webdriver
|
import webdriver
|
||||||
|
import macros
|
||||||
|
|
||||||
|
macro with*(obj: typed, code: untyped): untyped =
|
||||||
|
## Execute a set of statements with an object
|
||||||
|
expectKind code, nnkStmtList
|
||||||
|
result = code
|
||||||
|
|
||||||
|
# Simply inject obj into call
|
||||||
|
for i in 0 ..< result.len:
|
||||||
|
if result[i].kind in {nnkCommand, nnkCall}:
|
||||||
|
result[i].insert(1, obj)
|
||||||
|
|
||||||
|
template click*(session: Session, element: string, strategy=CssSelector) =
|
||||||
|
let el = session.findElement(element, strategy)
|
||||||
|
check el.isSome()
|
||||||
|
el.get().click()
|
||||||
|
|
||||||
|
template sendKeys*(session: Session, element, keys: string) =
|
||||||
|
let el = session.findElement(element)
|
||||||
|
check el.isSome()
|
||||||
|
el.get().sendKeys(keys)
|
||||||
|
|
||||||
|
template sendKeys*(session: Session, element: string, keys: varargs[Key]) =
|
||||||
|
let el = session.findElement(element)
|
||||||
|
check el.isSome()
|
||||||
|
|
||||||
|
# focus
|
||||||
|
el.get().click()
|
||||||
|
for key in keys:
|
||||||
|
session.press(key)
|
||||||
|
|
||||||
|
template ensureExists*(session: Session, element: string) =
|
||||||
|
let el = session.findElement(element)
|
||||||
|
check el.isSome()
|
||||||
|
|
||||||
|
template check*(session: Session, element: string, function: untyped) =
|
||||||
|
let el = session.findElement(element)
|
||||||
|
check function(el)
|
||||||
|
|
||||||
|
template check*(session: Session, element: string,
|
||||||
|
strategy: LocationStrategy, function: untyped) =
|
||||||
|
let el = session.findElement(element, strategy)
|
||||||
|
check function(el)
|
||||||
|
|
||||||
|
template checkIsNone*(session: Session, element: string, strategy=CssSelector) =
|
||||||
|
let el = session.findElement(element, strategy)
|
||||||
|
check el.isNone()
|
||||||
|
|
||||||
|
template checkText*(session: Session, element, expectedValue: string) =
|
||||||
|
let el = session.findElement(element)
|
||||||
|
check el.isSome()
|
||||||
|
check el.get().getText() == expectedValue
|
||||||
|
|
||||||
proc waitForLoad*(session: Session, timeout=20000) =
|
proc waitForLoad*(session: Session, timeout=20000) =
|
||||||
var waitTime = 0
|
var waitTime = 0
|
||||||
|
|
@ -14,29 +66,23 @@ proc waitForLoad*(session: Session, timeout=20000) =
|
||||||
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 logout*(session: Session) =
|
proc logout*(session: Session) =
|
||||||
# Check whether we can log out.
|
with session:
|
||||||
let logoutLink = session.findElement(
|
wait()
|
||||||
"Logout",
|
click "#profile-btn"
|
||||||
LinkTextSelector
|
click "#profile-btn #logout-btn"
|
||||||
).get()
|
wait()
|
||||||
logoutLink.click()
|
|
||||||
|
|
||||||
proc login*(session: Session, user, password: string) =
|
proc login*(session: Session, user, password: string) =
|
||||||
let logIn = session.findElement("#login-btn").get()
|
with session:
|
||||||
logIn.click()
|
click "#login-btn"
|
||||||
|
|
||||||
let usernameField = session.findElement(
|
sendKeys "#login-form input[name='username']", user
|
||||||
"#login-form input[name='username']"
|
sendKeys "#login-form input[name='password']", password
|
||||||
)
|
|
||||||
|
|
||||||
let passwordField = session.findElement(
|
sendKeys "#login-form input[name='password']", Key.Enter
|
||||||
"#login-form input[name='password']"
|
|
||||||
)
|
|
||||||
|
|
||||||
usernameField.get().sendKeys("admin")
|
wait()
|
||||||
passwordField.get().sendKeys("admin")
|
|
||||||
passwordField.get().click() # Focus field.
|
|
||||||
session.press(Key.Enter)
|
|
||||||
|
|
||||||
waitForLoad(session, 5000)
|
|
||||||
|
|
|
||||||
|
|
@ -9,96 +9,51 @@ proc test*(session: Session, baseUrl: string) =
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
test "shows sign up":
|
test "shows sign up":
|
||||||
let signUp = session.findElement("#signup-btn")
|
session.checkText("#signup-btn", "Sign up")
|
||||||
check signUp.get().getText() == "Sign up"
|
|
||||||
|
|
||||||
test "shows log in":
|
test "shows log in":
|
||||||
let logIn = session.findElement("#login-btn")
|
session.checkText("#login-btn", "Log in")
|
||||||
check logIn.get().getText() == "Log in"
|
|
||||||
|
|
||||||
test "is empty":
|
test "is empty":
|
||||||
let thread = session.findElement("tr > td.thread-title")
|
session.checkIsNone("tr > td.thread-title")
|
||||||
check thread.isNone()
|
|
||||||
|
|
||||||
# Logging in
|
# Logging in
|
||||||
test "can login/logout":
|
test "can login/logout":
|
||||||
let logIn = session.findElement("#login-btn").get()
|
with session:
|
||||||
logIn.click()
|
click "#login-btn"
|
||||||
|
|
||||||
let usernameField = session.findElement(
|
sendKeys "#login-form input[name='username']", "admin"
|
||||||
"#login-form input[name='username']"
|
sendKeys "#login-form input[name='password']", "admin"
|
||||||
)
|
|
||||||
check usernameField.isSome()
|
|
||||||
let passwordField = session.findElement(
|
|
||||||
"#login-form input[name='password']"
|
|
||||||
)
|
|
||||||
check passwordField.isSome()
|
|
||||||
|
|
||||||
usernameField.get().sendKeys("admin")
|
sendKeys "#login-form input[name='password']", Key.Enter
|
||||||
passwordField.get().sendKeys("admin")
|
wait()
|
||||||
passwordField.get().click() # Focus field.
|
|
||||||
session.press(Key.Enter)
|
|
||||||
|
|
||||||
waitForLoad(session, 5000)
|
# Verify that the user menu has been initialised properly.
|
||||||
|
click "#profile-btn"
|
||||||
|
checkText "#profile-btn #profile-name", "admin"
|
||||||
|
|
||||||
# Verify that the user menu has been initialised properly.
|
# Check whether we can log out.
|
||||||
let profileButton = session.findElement(
|
click "#logout-btn"
|
||||||
"#main-navbar figure.avatar"
|
# Verify we have logged out by looking for the log in button.
|
||||||
).get()
|
ensureExists "#login-btn"
|
||||||
profileButton.click()
|
|
||||||
|
|
||||||
let profileName = session.findElement(
|
|
||||||
"#main-navbar .menu-right div.tile-content"
|
|
||||||
).get()
|
|
||||||
|
|
||||||
check profileName.getText() == "admin"
|
|
||||||
|
|
||||||
# Check whether we can log out.
|
|
||||||
let logoutLink = session.findElement(
|
|
||||||
"Logout",
|
|
||||||
LinkTextSelector
|
|
||||||
).get()
|
|
||||||
logoutLink.click()
|
|
||||||
|
|
||||||
# Verify we have logged out by looking for the log in button.
|
|
||||||
check session.findElement("#login-btn").isSome()
|
|
||||||
|
|
||||||
test "can register":
|
test "can register":
|
||||||
let signup = session.findElement("#signup-btn").get()
|
with session:
|
||||||
signup.click()
|
click "#signup-btn"
|
||||||
|
|
||||||
let emailField = session.findElement(
|
sendKeys "#signup-form input[name='email']", "test@test.com"
|
||||||
"#signup-form input[name='email']"
|
sendKeys "#signup-form input[name='username']", "test"
|
||||||
).get()
|
sendKeys "#signup-form input[name='password']", "test"
|
||||||
let usernameField = session.findElement(
|
|
||||||
"#signup-form input[name='username']"
|
|
||||||
).get()
|
|
||||||
let passwordField = session.findElement(
|
|
||||||
"#signup-form input[name='password']"
|
|
||||||
).get()
|
|
||||||
|
|
||||||
emailField.sendKeys("test@test.com")
|
click "#signup-modal .create-account-btn"
|
||||||
usernameField.sendKeys("test")
|
wait()
|
||||||
passwordField.sendKeys("test")
|
|
||||||
|
|
||||||
let createAccount = session.findElement(
|
# Verify that the user menu has been initialised properly.
|
||||||
"#signup-modal .modal-footer .btn-primary"
|
click "#profile-btn"
|
||||||
).get()
|
checkText "#profile-btn #profile-name", "test"
|
||||||
|
# close menu
|
||||||
createAccount.click()
|
click "#profile-btn"
|
||||||
|
|
||||||
waitForLoad(session, 5000)
|
|
||||||
|
|
||||||
# Verify that the user menu has been initialised properly.
|
|
||||||
let profileButton = session.findElement(
|
|
||||||
"#main-navbar figure.avatar"
|
|
||||||
).get()
|
|
||||||
profileButton.click()
|
|
||||||
|
|
||||||
let profileName = session.findElement(
|
|
||||||
"#main-navbar .menu-right div.tile-content"
|
|
||||||
).get()
|
|
||||||
|
|
||||||
check profileName.getText() == "test"
|
|
||||||
|
|
||||||
|
session.navigate(baseUrl)
|
||||||
|
session.wait()
|
||||||
logout(session)
|
logout(session)
|
||||||
|
|
@ -3,44 +3,119 @@ import unittest, options, os, common
|
||||||
import webdriver
|
import webdriver
|
||||||
|
|
||||||
proc test*(session: Session, baseUrl: string) =
|
proc test*(session: Session, baseUrl: string) =
|
||||||
session.navigate(baseUrl)
|
let
|
||||||
|
userTitleStr = "This is a user thread!"
|
||||||
|
userContentStr = "A user has filled this out"
|
||||||
|
|
||||||
waitForLoad(session)
|
adminTitleStr = "This is a thread title!"
|
||||||
|
adminContentStr = "This is content"
|
||||||
|
|
||||||
login(session, "admin", "admin")
|
suite "user thread tests":
|
||||||
|
session.navigate(baseUrl)
|
||||||
|
session.wait()
|
||||||
|
login(session, "user", "user")
|
||||||
|
|
||||||
test "can create thread":
|
setup:
|
||||||
let newThreadBtn = session.findElement("#new-thread-btn").get()
|
session.navigate(baseUrl)
|
||||||
newThreadBtn.click()
|
session.wait()
|
||||||
|
|
||||||
waitForLoad(session)
|
test "can create thread":
|
||||||
|
with session:
|
||||||
|
click "#new-thread-btn"
|
||||||
|
wait()
|
||||||
|
|
||||||
let newThread = session.findElement("#new-thread")
|
sendKeys "#thread-title", userTitleStr
|
||||||
check newThread.isSome()
|
sendKeys "#reply-textarea", userContentStr
|
||||||
|
|
||||||
let createThreadBtn = session.findElement("#create-thread-btn")
|
click "#create-thread-btn"
|
||||||
check createThreadBtn.isSome()
|
wait()
|
||||||
|
|
||||||
|
checkText "#thread-title", userTitleStr
|
||||||
|
checkText ".original-post div.post-content", userContentStr
|
||||||
|
|
||||||
let threadTitle = session.findElement("#thread-title")
|
session.navigate(baseUrl)
|
||||||
check threadTitle.isSome()
|
session.wait()
|
||||||
|
logout(session)
|
||||||
|
|
||||||
let replyBox = session.findElement("#reply-textarea")
|
suite "admin thread tests":
|
||||||
check replyBox.isSome()
|
session.navigate(baseUrl)
|
||||||
|
session.wait()
|
||||||
|
login(session, "admin", "admin")
|
||||||
|
|
||||||
threadTitle.get().sendKeys("This is a thread title!")
|
setup:
|
||||||
replyBox.get().sendKeys("This is content.")
|
session.navigate(baseUrl)
|
||||||
|
session.wait()
|
||||||
|
|
||||||
createThreadBtn.get().click()
|
test "can create thread":
|
||||||
|
with session:
|
||||||
|
click "#new-thread-btn"
|
||||||
|
wait()
|
||||||
|
|
||||||
waitForLoad(session)
|
sendKeys "#thread-title", adminTitleStr
|
||||||
|
sendKeys "#reply-textarea", adminContentStr
|
||||||
|
|
||||||
let newThreadTitle = session.findElement("#thread-title")
|
click "#create-thread-btn"
|
||||||
check newThreadTitle.isSome()
|
wait()
|
||||||
|
|
||||||
check newThreadTitle.get().getText() == "This is a thread title!"
|
checkText "#thread-title", adminTitleStr
|
||||||
|
checkText ".original-post div.post-content", adminContentStr
|
||||||
|
|
||||||
let content = session.findElement(".original-post div.post-content")
|
test "try create duplicate thread":
|
||||||
check content.isSome()
|
with session:
|
||||||
|
click "#new-thread-btn"
|
||||||
|
wait()
|
||||||
|
ensureExists "#new-thread"
|
||||||
|
|
||||||
check content.get().getText() == "This is content."
|
sendKeys "#thread-title", adminTitleStr
|
||||||
|
sendKeys "#reply-textarea", adminContentStr
|
||||||
|
|
||||||
|
click "#create-thread-btn"
|
||||||
|
|
||||||
|
wait()
|
||||||
|
|
||||||
|
ensureExists "#new-thread p.text-error"
|
||||||
|
|
||||||
|
test "can edit post":
|
||||||
|
let modificationText = " and I edited it!"
|
||||||
|
with session:
|
||||||
|
click adminTitleStr, LinkTextSelector
|
||||||
|
wait()
|
||||||
|
|
||||||
|
click ".post-buttons .edit-button"
|
||||||
|
wait()
|
||||||
|
|
||||||
|
sendKeys ".original-post #reply-textarea", modificationText
|
||||||
|
click ".edit-buttons .save-button"
|
||||||
|
wait()
|
||||||
|
|
||||||
|
checkText ".original-post div.post-content", adminContentStr & modificationText
|
||||||
|
|
||||||
|
test "can like thread":
|
||||||
|
# Try to like the user thread above
|
||||||
|
|
||||||
|
with session:
|
||||||
|
click userTitleStr, LinkTextSelector
|
||||||
|
wait()
|
||||||
|
|
||||||
|
click ".post-buttons .like-button"
|
||||||
|
|
||||||
|
checkText ".post-buttons .like-button .like-count", "1"
|
||||||
|
|
||||||
|
test "can delete thread":
|
||||||
|
with session:
|
||||||
|
click adminTitleStr, LinkTextSelector
|
||||||
|
wait()
|
||||||
|
|
||||||
|
click ".post-buttons .delete-button"
|
||||||
|
wait()
|
||||||
|
|
||||||
|
# click delete confirmation
|
||||||
|
click "#delete-modal .delete-btn"
|
||||||
|
wait()
|
||||||
|
|
||||||
|
# Make sure the forum post is gone
|
||||||
|
checkIsNone adminTitleStr, LinkTextSelector
|
||||||
|
|
||||||
|
session.navigate(baseUrl)
|
||||||
|
session.wait()
|
||||||
|
logout(session)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue