-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHumanBenchmarkBot.py
411 lines (378 loc) · 17.3 KB
/
HumanBenchmarkBot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
try:
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import selenium.common.exceptions as seleniumexcept
except ImportError:
print("Missing selenium module")
print("Please install it, e.g. 'pip install selenium'")
exit(1)
try:
import pynput
except ImportError:
print("Missing pnyput module")
print("Please intall it, e.g. 'pip install pnyput'")
exit(1)
from ctypes import windll
import sys
import time
# For the whole bot we access the elements on the webpage, making the use of Python redundant, but wth...
def handleUserInput(userInput, limit, fast):
"""Handles all user input"""
if (userInput is None):
driver.get("https://www.humanbenchmark.com/")
elif (userInput == "help"):
printHelp(False)
elif (len(userInput.split()) == 2 and userInput.split()[0] == "-limit"):
limitInput = userInput.split()
if (limitInput[1].isdigit() and (int(limitInput[1]) > 0)):
limit = int(limitInput[1])
print("> Changed limit to:", str(limit))
else:
print("> Invalid -limit argument:", limitInput[1])
elif (userInput == "fast"):
fast = True
print("> Changed to fast")
elif (userInput == "stable"):
fast = False
print("> Changed to stable")
elif (userInput == "quit"):
print("> Goodbye.")
sys.exit(0)
elif (userInput == "close"):
print("> Goodbye.")
try:
driver.close()
sys.exit(0)
# raised if the browser is already closed
except seleniumexcept.WebDriverException:
sys.exit(0)
else:
url = "https://www.humanbenchmark.com/"
driver.get(url)
if (userInput == "number_memory"):
driver.get(url + "/tests/number-memory")
handleNumberMemory(limit)
elif(userInput == "reaction_time"):
driver.get(url + "/tests/reactiontime")
if (fast):
handleReactionTimeFast()
else:
handleReactionTimeStable()
elif (userInput == "verbal_memory"):
driver.get(url + "/tests/verbal-memory")
handleVerbalMemory(limit)
elif (userInput == "visual_memory"):
driver.get(url + "/tests/memory")
handleVisualMemory(limit, fast)
elif (userInput == "typing"):
driver.get(url + "/tests/typing")
handleTyping(fast)
elif (userInput == "aim_trainer"):
driver.get(url + "/tests/aim")
handleAimTest()
elif (userInput == "chimp"):
driver.get(url + "/tests/chimp")
handleChimpTest(limit)
else:
print("> Unknown test: " + userInput)
handleUserInput(
input("\n> What next? (Type help for help)\n# "), limit, fast)
def printHelp(isLaunchArgument):
"""Prints a help for this script, depending on the way "help" was called"""
if (isLaunchArgument):
print("> Usage example: python %s typing" % (sys.argv[0]))
print("> Set an optional limit: %s verbal_memory -limit 100" %
sys.argv[0])
print(
"> Start the stable version: %s visual_memory -limit 40 stable" % sys.argv[0])
print("> To print this help: python %s -help" % sys.argv[0])
print("> Available tests:\n\t- number_memory\n\t- reaction_time\n\t- verbal_memory\n\t- visual_memory\n\t- hearing\n\t- typing\n\t- aim_trainer\n\t- chimp")
else:
print("> Available commands:")
print("\t- 'quit' : Terminates this program")
print("\t- 'close' : Terminates this program and closes the browser")
print("\t- 'help' : Prints this help")
print("\t- '-limit x': Sets the limit to x")
print("\t- 'fast' / 'stable' : Switch between fast and stable execution of the next tests")
print("\t- 'testname' : Starts the test \"testname\"")
print("> Available tests:\n\t- number_memory\n\t- reaction_time (fast / stable)\n\t- verbal_memory\n\t- visual_memory (fast / stable)\n\t- typing (fast / stable)\n\t- aim_trainer\n\t- chimp")
def handleNumberMemory(limit):
wait = 3
# get and click the start button
startButton = driver.find_element_by_xpath("//button[text()='Start']")
startButton.click()
for i in range(limit):
print("> Executing test: %d / %d" % (i + 1, limit), end='\r')
sys.stdout.flush()
# save the current number
number = driver.find_element_by_class_name("big-number").text
# wait for and get the input field, type in the number and press RETURN
inputFieldPresent = EC.presence_of_element_located(
# look for an input element with type "text" that only allows numbers
(By.XPATH, "//input[@type='text' and @pattern='[0-9]*']"))
try:
# wait for the input field to become selectable, with 'wait' as our max waiting time
inputField = WebDriverWait(driver, wait).until(inputFieldPresent)
except TimeoutException:
print("> Timed out while waiting for input field.")
sys.exit(-1)
inputField.send_keys(number)
inputField.send_keys(Keys.RETURN)
nextButton = driver.find_element_by_xpath("//button[text()='NEXT']")
nextButton.click()
# the time the number is displayed for increases for each digit added
wait += 1
# clear stdout
print("")
def handleReactionTimeFast():
"""This version is faster, but requires a specific pixel on your screen to be monitored constantly,
which means your browser has to be maximised and you need to be scrolled to the top.
This method grabs your mouse to click"""
myMouse = pynput.mouse.Controller()
dc = windll.user32.GetDC(0)
gdi = windll.gdi32
limit = 5
# the center of the green / red window
window = driver.find_element_by_xpath(
"//div[@class='css-42wpoy e19owgy79']")
x = window.rect["x"] + (window.rect["width"] // 2)
y = window.rect["y"] + (window.rect["height"] // 2) + 160
myMouse.position = (x, y)
# need integers for gdi.GetPixel(x, y)
x = int(round(x))
y = int(round(y))
myMouse.click(pynput.mouse.Button.left, 1)
num = 0
while True:
# different monitors have slightly different values for this green, 7002955 was the green value on my Laptop monitor
# From what I have found out windll is the fastest way to check a single pixel value (< 10ms)
if (abs(gdi.GetPixel(dc, x, y) - 7002955) <= 500000 and num < limit):
myMouse.click(pynput.mouse.Button.left, 1)
num = num + 1
# give the browser time to change the color of the window from green to blue, or else the bot might click twice
time.sleep(0.1)
try:
timeDisplay = driver.find_element_by_xpath(
"//div[@class='css-1qvtbrk e19owgy78']/h1/div")
print("> Took", timeDisplay.text)
except seleniumexcept.NoSuchElementException:
print("> Couldn't find time display, maybe the absolute XPath changed?")
# don't click next if this was the last run
if (num < limit):
myMouse.click(pynput.mouse.Button.left, 1)
else:
return
def handleReactionTimeStable():
"""More stable but not recommended, since it's slower than most humans..."""
startScreenPresent = EC.presence_of_element_located(
(By.XPATH, "//div[@class='css-42wpoy e19owgy79']"))
try:
WebDriverWait(driver, 3).until(startScreenPresent).click()
except TimeoutException:
print("> Timed out while waiting for the start screen...")
sys.exit(-1)
limit = 5
for i in range(limit):
greenPanelPresent = EC.presence_of_element_located(
(By.XPATH, "//div[text()='Wait for green']"))
# wait until screen becomes green
try:
WebDriverWait(driver, 15).until(greenPanelPresent).click()
except TimeoutException:
print("> Green panel took too long to appear...")
return
time.sleep(0.1)
try:
timeDisplay = driver.find_element_by_xpath("//div[@class='css-1qvtbrk e19owgy78']/h1/div")
print("> Took", timeDisplay.text)
except seleniumexcept.NoSuchElementException:
print("> Couldn't find time display, maybe the absolute XPath changed?")
# don't click next if this was the last run
try:
if (i != limit - 1):
driver.find_element_by_xpath("//h2[text()='Click to keep going']").click()
else:
break
except seleniumexcept.NoSuchElementException:
print("> Couldn't continue with the test")
def handleVerbalMemory(limit):
# get and click the start button
# wait for the start button, as the site can take a second to load for this test
startButtonPresent = EC.element_to_be_clickable(
(By.XPATH, "//button[text()='Start']"))
try:
startButton = WebDriverWait(driver, 6).until(startButtonPresent)
except TimeoutException:
print("> Timed out while waiting for site to load.")
sys.exit(-1)
startButton.click()
alreadySeen = []
# keep collecting words and checking if we already saw them
for i in range(limit):
print("> Executing test: %d / %d" % (i + 1, limit), end='\r')
sys.stdout.flush()
currentWord = driver.find_element_by_class_name("word").text
if (alreadySeen.count(currentWord) > 0):
driver.find_element_by_xpath("//button[text()='SEEN']").click()
else:
alreadySeen.append(currentWord)
driver.find_element_by_xpath("//button[text()='NEW']").click()
# clear stdout
print("")
def handleVisualMemory(limit, fast):
"""Faster than handleVisualMemory but uses pynput to click,
which means that it is less stable since the white squares can't be obstructed
by any other window, the browser has to be maximized and the vertical offset
for the coordinate might not work on different screen resolutions.
More 'aggressive' since it grabs your mouse"""
startButtonPresent = EC.element_to_be_clickable(
(By.XPATH, "//button[text()='Start']"))
try:
startButton = WebDriverWait(driver, 6).until(startButtonPresent)
except TimeoutException:
print("> Timed out while waiting for site to load.")
sys.exit(-1)
startButton.click()
myMouse = pynput.mouse.Controller()
for i in range(limit):
print("> Executing test: %d / %d" % (i + 1, limit), end='\r')
sys.stdout.flush()
# wait for and save all whiteSquare WebElements
whiteSquaresPresent = EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, ".square.active"))
try:
whiteSquares = WebDriverWait(driver, 3).until(whiteSquaresPresent)
except TimeoutException:
print("> Timed out while waiting for white squares to appear.")
sys.exit(-1)
time.sleep(1.5)
# iterate through all white squares and click on their location
if (fast):
verticalOffset = 80
for square in whiteSquares:
coordinates = (square.rect["x"] + (square.rect["width"] / 2),
square.rect["y"] + (square.rect["height"] / 2) + verticalOffset)
myMouse.position = coordinates
myMouse.click(pynput.mouse.Button.left, 1)
else:
# this sample will only contain blue squares
allSquares = driver.find_elements_by_class_name("square")
# look through current squares and see if any of them where white by comparing their coordinates
indexWhiteSquares = 0
for square in allSquares:
if (indexWhiteSquares == len(whiteSquares)):
break
elif (square.rect["x"] == whiteSquares[indexWhiteSquares].rect["x"] and square.rect["y"] == whiteSquares[indexWhiteSquares].rect["y"]):
square.click()
indexWhiteSquares += 1
time.sleep(1)
# clear stdout
print("")
def handleTyping(fast):
time.sleep(0.5)
textbox = driver.find_element_by_class_name("letters")
myKeyboard = pynput.keyboard.Controller()
myMouse = pynput.mouse.Controller()
coordinates = (textbox.rect["x"] + (textbox.rect["width"] / 2),
textbox.rect["y"] + (textbox.rect["height"] / 2) + 80)
textList = []
# Each letter is a <div> element of class "incomplete"
textElements = driver.find_elements_by_class_name("incomplete")
print("> Starting to build string...")
# join all letters to create a string to type
# I have no idea why this takes so long
for textElement in textElements:
currentChar = textElement.text
if (currentChar == ""):
textList.append(" ")
else:
textList.append(currentChar)
# a simple way of displaying the progress for this loop, since it can take many seconds
print("> Completed %d%%" %
int((len(textList) / len(textElements)) * 100), end='\r')
sys.stdout.flush()
text = "".join(textList)
# add a \n so the stdout flushes correctly for the next print()
print("")
print("> Typing...")
# pynput's type() is more than 10 x faster than
# textbox.send_keys(text)
# but the textbox needs to be unobstracted by other windows
if (fast):
myMouse.position = coordinates
myMouse.click(pynput.mouse.Button.left, 1)
myKeyboard.type(text)
else:
textbox.send_keys(text)
resultPresent = EC.presence_of_element_located(
(By.XPATH, "//h1[@class='css-0']"))
try:
wpm = WebDriverWait(driver, 5).until(resultPresent).text
# accuracy = driver.find_element_by_xpath("/html/body/div/div/div[4]/div[1]/div/div[1]/p").text
except:
print("> There was a problem getting the results")
return
print("> Finished with: %s" % (wpm))
def handleAimTest():
targetPresent = EC.element_to_be_clickable(
(By.XPATH, "//div[@data-aim-target='true']/div[@style='width: 100px; height: 2px;']"))
try:
target = WebDriverWait(driver, 6).until(targetPresent)
target.click()
except TimeoutException:
print("> Timed out while waiting for site to load.")
sys.exit(-1)
for i in range(30):
try:
WebDriverWait(driver, 6).until(targetPresent).click()
except:
print("Failed to aim at target")
def handleChimpTest(limit):
startButton = driver.find_element_by_xpath("//button[text()='Start Test']")
startButton.click()
if (len(sys.argv) >= 2 and (sys.argv[1] == "-help" or sys.argv[1] == "help")):
printHelp(True)
sys.exit(0)
else:
# default limit, can be changed with launch option -limit
limit = 10
# wether the faster or stabler version of a test should be executed,
# fast by default, can be set to stable by adding "stable" as last launch argument
fast = True
if (len(sys.argv) >= 4 and sys.argv[2] == "-limit"):
if (sys.argv[3].isdigit()):
limit = int(sys.argv[3])
else:
print("> Invalid -limit argument")
sys.exit(-1)
options = webdriver.ChromeOptions()
options.add_argument("--start-maximized")
# 'excludeSwitches' and 'enable-automation' disable the header "this browser is controlled by automated software"
# this makes a difference for tests that rely on pixel coordinates, such as visual_memory
# '--load-extension' disables the pop-up warning to disable extensions in developer mode, which can mess with
# things such as 'send_keys()'
options.add_experimental_option(
"excludeSwitches", ['enable-automation', '--load-extension'])
# specify your chromedriver path
chrome_driver_binary = "C:\\Program Files\\Google\\chromedriver_win32\\chromedriver.exe"
# specify your Chrome browser location
options.binary_location = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
try:
driver = webdriver.Chrome(executable_path=chrome_driver_binary, options=options)
except seleniumexcept.WebDriverException:
print("Failed to locate chrome.exe or the chromedriver")
print("Please specify the correct path, as described in https://github.com/AlEscher/HumanBenchmarkBot#how-to-use-the-bot-recommended")
exit(1)
if (len(sys.argv) > 1):
print("> Starting test: " + sys.argv[1])
# check if 'safe' parameter is set
if ((len(sys.argv) >= 3 and sys.argv[2] == "stable") or (len(sys.argv) == 5 and sys.argv[4] == "stable")):
fast = False
handleUserInput(sys.argv[1], limit, fast)
else:
printHelp(False)
handleUserInput(None, limit, fast)