-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathweb_functions.py
362 lines (315 loc) · 14.6 KB
/
web_functions.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
import time
import random
import json
from datetime import timedelta, datetime
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import WebDriverException, TimeoutException, NoSuchElementException, StaleElementReferenceException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
class Web_Functions():
"""
A set of functions to be used for the Selenium Chrome Webdriver
Also designed for automated survey taking on the Pulse website
"""
def __init__(self, user="selenium"):
"""
Setup instance variables and class-level constants
"""
self.STATS_FILE = r"tests\survey_stats\pulse_bot_stats.json"
self.driver = None # initialized to none, set in open_site
self.user = user
def click(self, element):
"""
Moves to the WebElement item and then clicks it
returns -- none
"""
ActionChains(self.driver).move_to_element(
element).click(element).perform()
def open_site_new_session(self, url):
"""
Using a new Chrome session, create a Chrome webdriver and open the specified site
returns -- the function redirect_to_page(url)
"""
chrome_options = webdriver.ChromeOptions()
self.driver = webdriver.Chrome(
r"C:\chromedriver_win32\chromedriver.exe", options=chrome_options)
return self.redirect_to_page(url)
def open_site(self, url):
"""
Using a folder to save cookies, create a Chrome webdriver and open the website
"""
# use a user-data-dir folder to save cookies and login info
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(f"user-data-dir={self.user}")
# create a self.driver
self.driver = webdriver.Chrome(
r"C:\chromedriver_win32\chromedriver.exe", options=chrome_options)
# open pulse page and wait for it to redirect
return self.redirect_to_page(url)
def redirect_to_page(self, url):
"""
Navigates to the specified url in the driver
returns -- the driver after navigating to the url
"""
self.driver.get(url)
try:
WebDriverWait(self.driver, 10).until(
lambda x: x.current_url == url)
except TimeoutException:
print(f"Failed to redirect to {url}, timeout after 10 seconds")
return self.driver
def wait_until_element_appears(self, element, find_type=By.CLASS_NAME, wait_time=10):
"""
Wait specified time until a specified element appears in the browser
returns -- If element appears, return True; else, return False
"""
try:
WebDriverWait(self.driver, wait_time).until(
EC.presence_of_element_located((find_type, element)))
except TimeoutException:
print(
f"Page timed out after {wait_time}, element '{element}' not found")
return False
return True
def open_rewards_page(self):
""" Open the rewards page from the Pulse homepage """
self.wait_until_element_appears(
"rewards", find_type=By.ID)
rewards_page = self.driver.find_element_by_id("rewards")
self.click(rewards_page)
def open_surveys_page(self):
"""
Open the surveys page from the Pulse homepage
returns -- if No surveys are available, return False; else, True
"""
# verify that surveys page button appears, navigate to surveys page
self.wait_until_element_appears("surveys", find_type=By.ID)
surveys_page = self.driver.find_element_by_id("surveys")
self.click(surveys_page)
# click the button to sort surveys by # of responses (b/c more responses tends to == more points)
responses = self.wait_until_element_appears(
'responses', find_type=By.ID)
if not responses:
return False
else:
sort_by_responses_btn = self.driver.find_element_by_id('responses')
self.click(sort_by_responses_btn)
return True
def find_available_surveys(self, type="web"):
"""
Find all available surveys in current page and return the list of survey-card WebElements
arg type -- the type of surveys to be returned ('mobile', 'web', or 'all', by default web)
returns -- if 'all' is selected, returns a tuple of lists (web_surveys, mobile surveys)
"""
self.wait_until_element_appears(
".survey-card", find_type=By.CSS_SELECTOR)
all_surveys = self.driver.find_elements_by_css_selector(".survey-card")
mobile_surveys = self.driver.find_elements_by_css_selector(
".survey-card.mobile-only")
web_surveys = [
survey for survey in all_surveys if survey not in mobile_surveys]
print(
f"\nSURVEYS AVAILABLE: MOBILE - {len(mobile_surveys)} | WEB - {len(web_surveys)}")
# if there are no web_surveys on this page, navigate to another one and check recursively
if type == "web" and not web_surveys:
page_numbers = self.driver.find_elements_by_class_name(
'page-number ')
page_number = random.choice(page_numbers)
print(
f"No web surveys found, proceeding to another random page (pg. {page_number.text})")
self.click(page_number)
web_surveys = self.find_available_surveys()
if type == "web":
return web_surveys
elif type == "mobile":
return mobile_surveys
else:
return (web_surveys, mobile_surveys)
def open_survey(self, web_survey):
"""
Open the survey and verify that it appears
arg web_survey -- a WebElement object that represents a survey button (e.g. 'survey-card' class)
returns -- {'text': survey .text value, split by newlines,
'question_btns': list of WebElements for each question_button in survey}
"""
survey_text = web_survey.text.split('\n')
self.click(web_survey)
# verify that the survey opens by looking for question-button elements
if self.wait_until_element_appears("question-button"):
question_btns = self.driver.find_elements_by_class_name(
"question-button")
question_btns.insert(0, self.driver.find_element_by_class_name(
"question-button-highlighted"))
question_btns = question_btns[:-1]
else:
print(
f"Error caught in open_survey; question-button not found, opening new survey")
web_surveys = self.find_available_surveys()
# open a new survey recursively
return self.open_survey(web_surveys[0])
print(
f"*** ANSWERING SURVEY: {survey_text[2]}, {survey_text[0]} points***")
print(f"NUM OF QUESTIONS: {len(question_btns)}\n")
return {'text': survey_text, 'question_btns': question_btns}
def save_survey_stats(self, survey_text, time_taken):
"""
Saves the passed survey bot_stats to a JSON file
arg survey -- the .text value for a WebElement item, split by newlines
arg time_taken -- the time taken for survey completion by bot
"""
survey_points = survey_text[0]
survey_name = survey_text[2]
survey_respondents = survey_text[3]
survey_questions = survey_text[-2]
survey_time = survey_text[-1]
# write these stats to a file to keep track
with open(self.STATS_FILE, 'r') as fp:
bot_stats = json.load(fp)
bot_stats["Total_Points_Accumulated"] += int(survey_points)
bot_stats["Surveys_Completed"].append({
"name": survey_name,
"date_taken": datetime.now().strftime('%m/%d/%Y %H:%M:%S'),
"points": survey_points,
"respondents": survey_respondents,
"questions": survey_questions,
"expected_time": f"00:{survey_time}",
"bot_time": str(timedelta(seconds=round(time_taken)))
})
bot_stats["Total_Surveys_Taken"] = int(
len(bot_stats["Surveys_Completed"]))
with open(self.STATS_FILE, 'w') as fp:
json.dump(bot_stats, fp, indent=2)
def add_times(self, curr_time, new_time):
"""
Takes two times (one current, one to add to current value) and adds them using timedeltas
returns -- string output of curr_time + new_time
"""
curr_split = list(map(int, curr_time.split(":")))
new_split = list(map(int, new_time.split(":")))
if len(curr_split) == 3:
hrs, mins, secs = curr_split
curr_time = timedelta(hours=hrs, minutes=mins, seconds=secs)
else:
mins, secs = curr_split
curr_time = timedelta(minutes=mins, seconds=secs)
if len(new_split) == 3:
hrs, mins, secs = new_split
new_time = timedelta(hours=hrs, minutes=mins, seconds=secs)
else:
mins, secs = new_split
new_time = timedelta(minutes=mins, seconds=secs)
return str(curr_time + new_time)
def generate_answers(self, num_answers):
"""
returns -- an array of strings, randomly generated answers
"""
self.driver = self.open_site_new_session(
"https://randomwordgenerator.com/sentence.php")
question_answers = [
"I don't care",
"I'm not sure",
"Why do you ask?",
"Big Oof notation",
"Yikes"]
answer_num = self.driver.find_element_by_css_selector(
".form-control.input-sm")
answer_num.clear()
answer_num.send_keys(str(num_answers))
generate_button = self.driver.find_element_by_css_selector(
".btn.btn-primary")
self.click(generate_button)
generated_answers = self.driver.find_elements_by_class_name(
"support-sentence")
question_answers = [answer.text for answer in generated_answers]
self.driver.quit()
return question_answers
def answer_question(self, question, text_answers):
"""
Answer the specified question
arg question -- a WebElement specifying a question_button
arg text_answers -- a list of strings to be used as answers in text input fields
"""
# scroll to top of window
self.driver.execute_script("scrollBy(0,250);")
time.sleep(.3)
self.click(question)
time.sleep(.5)
# print out the current question
self.wait_until_element_appears(
self.driver, "question-text", wait_time=2)
question_text = self.driver.find_element_by_class_name(
"question-text").text
print(f"... Answering question:\n {question_text} ...")
input_boxes, answer_boxes, num_boxes = [], [], []
# find all the types of answer inputs
if self.wait_until_element_appears("mc-option", wait_time=1):
input_boxes = self.driver.find_elements_by_class_name("mc-option")
elif self.wait_until_element_appears("answer-box", find_type=By.ID, wait_time=1):
answer_boxes = self.driver.find_elements_by_id("answer-box")
elif self.wait_until_element_appears("numeric-input-box", find_type=By.ID, wait_time=1):
num_boxes = self.driver.find_elements_by_id("numeric-input-box")
print(
f"# OPTION BOXES: {len(input_boxes)} \
# ANSWER BOXES: {len(answer_boxes)} \
# # NUM BOXES: {len(num_boxes)}")
time.sleep(.3)
# answer the question depending on the input type
if answer_boxes:
self.driver.execute_script("scrollBy(0,250);")
answer_boxes[0].send_keys(random.choice(text_answers))
elif input_boxes:
self.driver.execute_script("scrollBy(0,250);")
option_chosen = random.choice(input_boxes[:5])
self.click(option_chosen)
self.driver.execute_script("scrollBy(0,250);")
elif num_boxes:
self.driver.execute_script("scrollBy(0,250);")
if "GPA" in question_text:
num_boxes[0].send_keys("2.69")
elif "Keystones" in question_text:
num_boxes[0].send_keys("420")
else:
num_boxes[0].send_keys(str(random.randint(0, 5)))
else:
print(f"New input type on question {question}!")
time.sleep(.3)
def submit_survey(self):
"""
Submits a finished survey
returns -- none
"""
if self.wait_until_element_appears("question-button", wait_time=1):
submit_btn = self.driver.find_elements_by_class_name(
"question-button")[-1]
self.click(submit_btn)
self.wait_until_element_appears("survey-submit-button")
final_submit_btn = self.driver.find_element_by_class_name(
"survey-submit-button")
self.click(final_submit_btn)
self.check_if_too_fast()
time.sleep(.5)
# navigate back to the homepage to ensure we go back to survey answering page
self.wait_until_element_appears('icon-container')
pulse_logo_btn = self.driver.find_element_by_class_name(
"icon-container")
self.click(pulse_logo_btn)
def check_if_too_fast(self):
"""
If too fast message appears, wait a few seconds and then resubmit
returns -- none
"""
if self.wait_until_element_appears('too-fast-message', wait_time=1):
print(f"Submitted the survey too quickly! Trying again")
self.wait_until_element_appears('go-back-container', wait_time=1)
go_back_btn = self.driver.find_element_by_class_name(
"go-back-container")
self.click(go_back_btn)
time.sleep(.75)
self.wait_until_element_appears(
self.driver, 'question-button', wait_time=1)
submit_btn = self.driver.find_elements_by_class_name(
"question-button")[-1]
self.click(submit_btn)
self.submit_survey()