Upgrade to Selenium 4

Switches from standalone containers to explicit node+hub config

Selenium 4 has some differences in handling stale elements that we
should be aware of moving forward

closes OUT-4988
flag=none
[skip-stages=Flakey Spec Catcher]

Test-plan:
- make sure screenshots can happen for failures
- retrigger a few times and make sure things pass
- verify build summaries are intact
- verify FSC can still run seleniums
- verify local selenium running still works
  - firefox / chrome / edge where applicable
- verify docker selenium running still works
  - firefox / chrome / edge where applicable

Change-Id: I8f2fe5a34d712b5ccd7191bae7a9aeeb6f1f473d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/284811
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: James Butters <jbutters@instructure.com>
QA-Review: Robin Kuss <rkuss@instructure.com>
Product-Review: Brian Watson <bwatson@instructure.com>
This commit is contained in:
Brian Watson 2022-02-09 15:53:07 -07:00
parent df7130ba93
commit 2f2a27d91b
37 changed files with 180 additions and 201 deletions

View File

@ -41,9 +41,9 @@ group :test do
gem "once-ler", "2.0.0"
gem "sauce_whisk", "0.2.2"
gem "selenium-webdriver", "3.142.7", require: false
gem "selenium-webdriver", "~> 4.1.0", require: false
gem "childprocess", "3.0.0", require: false
gem "webdrivers", "4.2.0", require: false
gem "webdrivers", "5.0.0", require: false
gem "testrailtagging", "0.3.8.7", require: false
gem "webmock", "3.8.2", require: false

View File

@ -68,7 +68,6 @@ pipeline {
RSPECQ_UPDATE_TIMINGS = "${env.GERRIT_EVENT_TYPE == 'change-merged' ? '1' : '0'}"
ENABLE_AXE_SELENIUM = "${env.ENABLE_AXE_SELENIUM}"
POSTGRES_PASSWORD = 'sekret'
SELENIUM_VERSION = '3.141.59-20210929'
RSPECQ_REDIS_URL = redisUrl()
PATCHSET_TAG = getPatchsetTag()
CANVAS_ZEITWERK = '1'
@ -103,18 +102,18 @@ pipeline {
projectName: env.JOB_NAME,
selector: specific(env.BUILD_NUMBER),
)
withEnv(['COMPOSE_FILE=docker-compose.new-jenkins.yml']) {
sh """
docker-compose run -v \$(pwd)/\$LOCAL_WORKDIR/tmp/coverage/:/tmp/coverage \
--name coverage-collator canvas bash -c \
"bundle install; bundle exec rake coverage:report['/tmp/coverage/canvas__*/**']"
"""
sh 'docker cp coverage-collator:/usr/src/app/coverage/ coverage'
archiveArtifacts allowEmptyArchive: true, artifacts: 'coverage/**'
publishHTML target: [
allowMissing: false,
alwaysLinkToLastBuild: false,
@ -123,7 +122,7 @@ pipeline {
reportFiles: 'index.html',
reportName: 'Ruby Coverage Report'
]
uploadCoverage([
uploadSource: '/coverage',
uploadDest: env.COVERAGE_LOCATION

View File

@ -168,7 +168,6 @@ pipeline {
RSPECQ_UPDATE_TIMINGS = "${env.GERRIT_EVENT_TYPE == 'change-merged' ? '1' : '0'}"
ENABLE_AXE_SELENIUM = "${env.ENABLE_AXE_SELENIUM}"
POSTGRES_PASSWORD = 'sekret'
SELENIUM_VERSION = '3.141.59-20210929'
RSPECQ_REDIS_URL = redisUrl()
CANVAS_ZEITWERK = '1'
}

View File

@ -189,7 +189,6 @@ pipeline {
// until we figure out how to run them, we should ignore them
FSC_IGNORE_FILES = 'gems/.*/spec/,spec/contracts/,engines/.*/spec/,spec/selenium/performance/'
POSTGRES_PASSWORD = 'sekret'
SELENIUM_VERSION = '3.141.59-20210929'
// Targeting 10 minutes / node, each node runs RSPEC_PROCESSES threads and
// repeats each test FSC_REPEAT_FACTOR times.

View File

@ -37,7 +37,6 @@ pipeline {
RUBY = configuration.ruby()
RERUNS_RETRY = 0 // no reruns
POSTGRES_PASSWORD = 'sekret'
SELENIUM_VERSION = '3.141.59-20210929'
CASSANDRA_IMAGE_TAG = imageTag.cassandra()
DYNAMODB_IMAGE_TAG = imageTag.dynamodb()

View File

@ -1,5 +1,4 @@
ARG SELENIUM_VERSION=3.141.59-20210929
FROM selenium/standalone-chrome-debug:$SELENIUM_VERSION
FROM selenium/node-chrome:98.0
COPY entry_point.sh /opt/bin/custom_entry_point.sh
USER root

View File

@ -17,7 +17,8 @@ DOCKER_IMAGES=(
$POSTGRES_IMAGE_TAG
$REGISTRY_BASE/canvas-rce-api
$REGISTRY_BASE/redis:alpine
$REGISTRY_BASE/selenium-chrome:"${SELENIUM_VERSION:-3.141.59-20210929}"
$REGISTRY_BASE/selenium-node-chrome:"${CHROME_VERSION:-98.0}"
$REGISTRY_BASE/selenium-hub:4.1.2-20220217
)
echo "${DOCKER_IMAGES[@]}" | xargs -P0 -n1 ./build/new-jenkins/docker-with-flakey-network-protection.sh pull &

View File

@ -29,8 +29,7 @@ def createDistribution(nestedStages) {
def baseEnvVars = [
"ENABLE_AXE_SELENIUM=${env.ENABLE_AXE_SELENIUM}",
'POSTGRES_PASSWORD=sekret',
'SELENIUM_VERSION=3.141.59-20210929'
'POSTGRES_PASSWORD=sekret'
]
def rspecqEnvVars = baseEnvVars + [
@ -70,8 +69,7 @@ def createDistribution(nestedStages) {
def createLegacyDistribution(nestedStages) {
def setupNodeHook = this.&setupNode
def baseEnvVars = [
'POSTGRES_PASSWORD=sekret',
'SELENIUM_VERSION=3.141.59-20210929'
'POSTGRES_PASSWORD=sekret'
]
// Used only for crystalball map generation

View File

@ -245,9 +245,11 @@ To enable Selenium: Add `docker-compose/selenium.override.yml` to your `COMPOSE_
The container used to run the selenium browser is only started when spinning up
all docker-compose containers, or when specified explicitly. The selenium
container needs to be started before running any specs that require selenium.
Select a browser to run in selenium through config/selenium.yml and then ensure
that only the corresponding browser is configured in selenium.override.yml.
```sh
docker-compose up selenium-firefox # or selenium-chrome or selenium-edge
docker-compose up -d selenium-hub
```
With the container running, you should be able to open a VNC session:

View File

@ -6,20 +6,13 @@ services:
- selenium-chrome
- canvasrceapi
environment:
remote_url: http://selenium-chrome:4444/wd/hub
remote_url: http://selenium-hub:4444/wd/hub
browser: chrome
RCE_HOST: "http://canvasrceapi"
# these are so we can use prod compiled assets in test environment
USE_OPTIMIZED_JS: 'true'
SASS_STYLE: 'compressed'
selenium-chrome:
image: starlord.inscloudgate.net/jenkins/selenium-chrome:$SELENIUM_VERSION
environment:
SCREEN_WIDTH: 1680
SCREEN_HEIGHT: 1050
init: true
canvasrceapi:
image: starlord.inscloudgate.net/jenkins/canvas-rce-api
environment:
@ -33,3 +26,53 @@ services:
STATSD_HOST: 127.0.0.1
STATSD_PORT: 8125
init: true
selenium-hub:
image: starlord.inscloudgate.net/jenkins/selenium-hub:4.1.2-20220217
environment:
GRID_MAX_SESSION: 64
GRID_BROWSER_TIMEOUT: 3000
selenium-chrome: &NODE_CHROME
image: starlord.inscloudgate.net/jenkins/selenium-node-chrome:98.0
environment: &NODE_CHROME_ENV
SE_EVENT_BUS_HOST: selenium-hub
SE_EVENT_BUS_PUBLISH_PORT: 4442
SE_EVENT_BUS_SUBSCRIBE_PORT: 4443
HUB_PORT_4444_TCP_ADDR: selenium-hub
HUB_PORT_4444_TCP_PORT: 4444
SE_NODE_HOST: selenium-chrome
JAVA_OPTS: '-Dwebdriver.chrome.whitelistedIps='
init: true
links:
- selenium-hub
selenium-chrome2:
<<: *NODE_CHROME
environment:
<<: *NODE_CHROME_ENV
SE_NODE_HOST: selenium-chrome2
selenium-chrome3:
<<: *NODE_CHROME
environment:
<<: *NODE_CHROME_ENV
SE_NODE_HOST: selenium-chrome3
selenium-chrome4:
<<: *NODE_CHROME
environment:
<<: *NODE_CHROME_ENV
SE_NODE_HOST: selenium-chrome4
selenium-chrome5:
<<: *NODE_CHROME
environment:
<<: *NODE_CHROME_ENV
SE_NODE_HOST: selenium-chrome5
selenium-chrome6:
<<: *NODE_CHROME
environment:
<<: *NODE_CHROME_ENV
SE_NODE_HOST: selenium-chrome6

View File

@ -1,6 +1,6 @@
test:
remote_url_firefox: http://selenium-firefox:4444/wd/hub
remote_url_chrome: http://selenium-chrome:4444/wd/hub
remote_url_edge: http://selenium-edge:4444/wd/hub
remote_url_firefox: http://selenium-hub:4444/wd/hub
remote_url_chrome: http://selenium-hub:4444/wd/hub
remote_url_edge: http://selenium-hub:4444/wd/hub
browser: chrome
# auto_open_devtools: true

View File

@ -1,5 +1,4 @@
ARG SELENIUM_VERSION=3.141.59-20210929
FROM selenium/standalone-chrome-debug:$SELENIUM_VERSION
FROM selenium/node-chrome:98.0
COPY entry_point.sh /opt/bin/custom_entry_point.sh
USER root

View File

@ -1,4 +1,4 @@
FROM selenium/standalone-edge:96.0
FROM selenium/node-edge:98.0
COPY entry_point.sh /opt/bin/custom_entry_point.sh
USER root

View File

@ -1,5 +1,5 @@
# Keep this image version tag synced with Gemfile.d/test.rb
FROM selenium/standalone-firefox-debug:3.12.0-americium
FROM selenium/node-firefox:96.0
COPY entry_point.sh /opt/bin/custom_entry_point.sh
USER root

View File

@ -5,35 +5,38 @@ version: '2.3'
services:
web:
links:
- selenium-chrome
#- selenium-firefox
#- selenium-edge
- selenium-hub
selenium-chrome:
build: ./docker-compose/selenium-chrome
## We list all of the different standalone containers as `selenium-hub` since Jenkins
## will use the actual hub + node configuration instead of a standalone for performance
## reasons. Listing all of them as selenium-hub saves a great deal of configuration issues
## Chrome
selenium-hub:
image: selenium/standalone-chrome
environment:
SE_NODE_GRID_URL: selenium-hub:4444/wd/hub
VIRTUAL_HOST: seleniumch.docker
init: true
ports:
- 5901:5900
environment:
VIRTUAL_HOST: seleniumch.docker
remote_url: http://seleniumch.docker/wd/hub
browser: chrome
# selenium-firefox:
# build: ./docker-compose/selenium-firefox
## Firefox
# selenium-hub:
# image: selenium/standalone-firefox
# environment:
# SE_NODE_GRID_URL: selenium-hub:4444/wd/hub
# VIRTUAL_HOST: seleniumff.docker
# init: true
# ports:
# - 5900:5900
# environment:
# VIRTUAL_HOST: seleniumff.docker
# remote_url: http://seleniumff.docker/wd/hub
# browser: firefox
# selenium-edge:
# build: ./docker-compose/selenium-edge
## Edge
# selenium-hub:
# image: selenium/standalone-edge
# environment:
# SE_NODE_GRID_URL: selenium-hub:4444/wd/hub
# VIRTUAL_HOST: seleniumedge.docker
# init: true
# ports:
# - 5902:5900
# environment:
# VIRTUAL_HOST: seleniumedge.docker
# remote_url: http://seleniumedge.docker/wd/hub
# browser: edge
# shm_size: 2gb

View File

@ -120,7 +120,7 @@ function display_next_steps {
echo ':docker-compose/selenium.override.yml' >> .env
build the selenium container
${DOCKER_COMMAND} build selenium-chrome
${DOCKER_COMMAND} build selenium-hub
run selenium
${DOCKER_COMMAND} run --rm web bundle exec rspec spec/selenium

View File

@ -29,26 +29,22 @@ describe "account authentication" do
end
describe "sso settings" do
let(:login_handle_name) { f("#sso_settings_login_handle_name") }
let(:change_password_url) { f("#sso_settings_change_password_url") }
let(:auth_discovery_url) { f("#sso_settings_auth_discovery_url") }
it "saves", priority: "1" do
add_sso_config
expect(login_handle_name).to have_value "login"
expect(change_password_url).to have_value "http://test.example.com"
expect(auth_discovery_url).to have_value "http://test.example.com"
expect(f("#sso_settings_login_handle_name")).to have_value "login"
expect(f("#sso_settings_change_password_url")).to have_value "http://test.example.com"
expect(f("#sso_settings_auth_discovery_url")).to have_value "http://test.example.com"
end
it "updates", priority: "1" do
add_sso_config
login_handle_name.clear
change_password_url.clear
auth_discovery_url.clear
f("#sso_settings_login_handle_name").clear
f("#sso_settings_change_password_url").clear
f("#sso_settings_auth_discovery_url").clear
f("#edit_sso_settings button[type='submit']").click
expect(login_handle_name).not_to have_value "login"
expect(change_password_url).not_to have_value "http://test.example.com"
expect(auth_discovery_url).not_to have_value "http://test.example.com"
expect(f("#sso_settings_login_handle_name")).not_to have_value "login"
expect(f("#sso_settings_change_password_url")).not_to have_value "http://test.example.com"
expect(f("#sso_settings_auth_discovery_url")).not_to have_value "http://test.example.com"
end
end

View File

@ -43,10 +43,6 @@ describe "new account user search" do
user_session(@user)
end
def wait_for_loading_to_disappear
expect(f('[data-automation="users list"]')).not_to contain_css("tr:nth-child(2)")
end
describe "with default page visit" do
before do
@user.update_attribute(:name, "Test User")
@ -95,7 +91,9 @@ describe "new account user search" do
user_with_pseudonym(account: @account, name: "diffrient user")
refresh_page
user_search_box.send_keys("Test")
wait_for_loading_to_disappear
wait_for_ajaximations
wait_for(method: nil, timeout: 0.5) { fj("title:contains('Loading')").displayed? }
wait_for_no_such_element { fj("title:contains('Loading')") }
expect(results_rows.count).to eq 1
expect(results_rows.first).to include_text("Test")
end

View File

@ -92,7 +92,7 @@ module AssignmentsIndexPage
end
def bulk_edit_tr_rows
ff("#bulkEditRoot tbody tr")
ff("#bulkEditRoot [role='table'] [role='row']")
end
def bulk_edit_loading_spinner

View File

@ -31,7 +31,7 @@ describe "browser" do
get("/login")
driver.execute_script("window.console.log('#{sample_msg}')")
browser_logs = driver.manage.logs.get(:browser)
browser_logs = driver.logs.get(:browser)
expect(browser_logs.map(&:message)).to include(a_string_matching(sample_msg))
end

View File

@ -192,7 +192,7 @@ shared_context "in-process server selenium tests" do
example.metadata[:page_html] = document.to_html
end
browser_logs = driver.manage.logs.get(:browser) rescue nil
browser_logs = driver.logs.get(:browser) rescue nil
# log INSTUI deprecation warnings
if browser_logs.present?

View File

@ -126,8 +126,8 @@ describe "native canvas conditional release" do
assignment = assignment_model(course: @course, points_possible: 100)
get "/courses/#{@course.id}/assignments/#{assignment.id}/edit"
ConditionalReleaseObjects.conditional_release_link.click
replace_content(ConditionalReleaseObjects.division_cutoff1, "72")
replace_content(ConditionalReleaseObjects.division_cutoff2, "47")
ConditionalReleaseObjects.replace_mastery_path_scores(ConditionalReleaseObjects.division_cutoff1, "70", "72")
ConditionalReleaseObjects.replace_mastery_path_scores(ConditionalReleaseObjects.division_cutoff2, "40", "47")
ConditionalReleaseObjects.division_cutoff2.send_keys :tab
expect(ConditionalReleaseObjects.division_cutoff1.attribute("value")).to eq("72 pts")
@ -188,10 +188,10 @@ describe "native canvas conditional release" do
get "/courses/#{@course.id}/assignments/#{assignment.id}/edit"
ConditionalReleaseObjects.conditional_release_link.click
replace_content(ConditionalReleaseObjects.division_cutoff1, "")
ConditionalReleaseObjects.replace_mastery_path_scores(ConditionalReleaseObjects.division_cutoff1, "70", "")
expect(ConditionalReleaseObjects.must_not_be_empty_exists?).to eq(true)
replace_content(ConditionalReleaseObjects.division_cutoff1, "35")
ConditionalReleaseObjects.replace_mastery_path_scores(ConditionalReleaseObjects.division_cutoff1, "", "35")
expect(ConditionalReleaseObjects.these_scores_are_out_of_order_exists?).to eq(true)
end

View File

@ -26,6 +26,11 @@ class ConditionalReleaseObjects
element_exists?("#conditional_content")
end
def replace_mastery_path_scores(element, current_value, new_value)
current_value.length.times { element.send_keys(:backspace) }
element.send_keys(new_value)
end
# Assignment Index Page
def assignment_kebob(page_title)

View File

@ -611,7 +611,8 @@ describe "content migrations", :non_parallel do
ff("[name=selective_import]")[0].click
submit
run_jobs
expect(f(".migrationProgressItem .progressStatus")).to include_text("Completed")
# Wait until the item is imported on the back-end, otherwise the selenium tools will fail the test due to runtime
keep_trying_until { ContentMigration.last.workflow_state == "imported" }
@course.reload
expect(@course.announcements.last.locked).to be_truthy
expect(@course.lock_all_announcements).to be_truthy
@ -631,7 +632,8 @@ describe "content migrations", :non_parallel do
ff("[name=selective_import]")[0].click
submit
run_jobs
expect(f(".migrationProgressItem .progressStatus")).to include_text("Completed")
# Wait until the item is imported on the back-end, otherwise the selenium tools will fail the test due to runtime
keep_trying_until { ContentMigration.last.workflow_state == "imported" }
@course.reload
expect(@course.discussion_topics.last.allow_rating).to be_truthy
end

View File

@ -30,6 +30,11 @@ describe "course copy" do
expect(header.text).to eq @course.course_code
end
def wait_for_migration_to_complete
completed_status = fj("div.progressStatus:contains('Completed')")
keep_trying_until(5) { completed_status.displayed? == true }
end
it "copies the course" do
course_with_admin_logged_in
@course.syllabus_body = "<p>haha</p>"
@ -37,11 +42,12 @@ describe "course copy" do
@course.default_view = "modules"
@course.wiki_pages.create!(title: "hi", body: "Whatever")
@course.save!
get "/courses/#{@course.id}/copy"
expect_new_page_load { f('button[type="submit"]').click }
expect(f("div.progressStatus").text.include?("Queued")).to eq(true)
run_jobs
expect(f("div.progressStatus span")).to include_text "Completed"
wait_for_ajaximations
wait_for_migration_to_complete
@new_course = Course.last
expect(@new_course.syllabus_body).to eq @course.syllabus_body
@ -123,12 +129,11 @@ describe "course copy" do
get "/courses/#{@course.id}/settings"
link = f(".copy_course_link")
expect(link).to be_displayed
expect_new_page_load { link.click }
expect_new_page_load { f('button[type="submit"]').click }
run_jobs
expect(f("div.progressStatus span")).to include_text "Completed"
wait_for_ajaximations
wait_for_migration_to_complete
@new_course = subaccount.courses.where("id <>?", @course.id).last
expect(@new_course.syllabus_body).to eq @course.syllabus_body

View File

@ -589,6 +589,7 @@ describe "context modules" do
expect(button.text).to eq("Expand All")
refresh_page
assert_collapsed
button = f("button#expand_collapse_all")
button.click
wait_for_ajaximations
assert_expanded

View File

@ -49,7 +49,7 @@ describe "cross-listing" do
# crosslist a valid course
course_id.click
course_id.clear
course_id.send_keys([:control, "a"], @course2.id.to_s, "\n")
course_id.send_keys(@course2.id.to_s, "\n")
expect(course_name).to include_text(@course2.name)
expect(form.find_element(:id, "course_autocomplete_id")).to have_attribute(:value, @course.id.to_s)
expect(submit_btn).not_to have_class("disabled")
@ -107,7 +107,7 @@ describe "cross-listing" do
# k, let's crosslist to the other course
form.find_element(:css, "#course_id").click
form.find_element(:css, "#course_id").clear
form.find_element(:css, "#course_id").send_keys([:control, "a"], other_course.id.to_s, "\n")
form.find_element(:css, "#course_id").send_keys(other_course.id.to_s, "\n")
expect(f("#course_autocomplete_name")).to include_text other_course.name
expect(form.find_element(:css, "#course_autocomplete_id")).to have_attribute(:value, other_course.id.to_s)

View File

@ -104,8 +104,8 @@ describe "discussions" do
wait_for_ajaximations
fj(".ic-tokeninput-option:visible:first").click
wait_for_ajaximations
fj(".datePickerDateField[data-date-type='due_at']:first").send_keys(format_date_for_view(due_at1))
fj(".datePickerDateField[data-date-type='due_at']:first").send_keys(format_date_for_view(due_at1), :tab)
wait_for_ajaximations
f("#add_due_date").click
wait_for_ajaximations

View File

@ -68,7 +68,7 @@ describe "Gradebook" do
Gradebook::Cells.open_tray(@student_1, group_assignment)
Gradebook::GradeDetailTray.add_new_comment(@comment_text)
Gradebook::GradeDetailTray.close_tray_button.click
Gradebook::GradeDetailTray.click_close_tray_button
# make sure it's on the other student's submission
Gradebook::Cells.open_tray(@student_2, group_assignment)

View File

@ -412,6 +412,8 @@ describe "Gradebook" do
grading_cell.click
Gradebook::Cells.edit_grade(student, essay_quiz.assignment, 10)
# Re-select element in case it's gone stale
grading_cell = Gradebook::Cells.grading_cell(student, essay_quiz.assignment)
expect(grading_cell).not_to contain_css(".icon-not-graded")
end
end

View File

@ -31,8 +31,8 @@ module Gradebook
f("#SubmissionTray__Content")
end
def self.close_tray_button
fj("button:contains('Close submission tray')")
def self.click_close_tray_button
force_click("button:contains('Close submission tray')")
end
def self.avatar

View File

@ -129,6 +129,7 @@ module AssignmentOverridesSeleniumHelper
last_due_at_element.send_keys(opts.fetch(:due_at, Time.zone.now.advance(days: 5)))
last_unlock_at_element.send_keys(opts.fetch(:unlock_at, Time.zone.now.advance(days: -1)))
last_lock_at_element.send_keys(opts.fetch(:lock_at, Time.zone.now.advance(days: 5)))
last_lock_at_element.send_keys(:tab)
end
def find_vdd_time(override_context)

View File

@ -221,21 +221,8 @@ describe "RCE next tests", ignore_js_errors: true do
body: "<p id='para'><a id='lnk' href='http://example.com'>delete me</a></p>"
)
visit_existing_wiki_edit(@course, "title")
f("##{rce_page_body_ifr_id}").click
f("##{rce_page_body_ifr_id}").send_keys(
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left],
%i[shift arrow_left]
)
f("##{rce_page_body_ifr_id}").send_keys(:enter)
f("##{rce_page_body_ifr_id}").send_keys([:control, "a"], :backspace)
in_frame rce_page_body_ifr_id do
expect(f("#para").text).to eql ""
@ -1175,7 +1162,7 @@ describe "RCE next tests", ignore_js_errors: true do
it "opens keyboard shortcut modal with alt-f8" do
visit_front_page_edit(@course)
rce = f(".tox-edit-area__iframe")
rce.send_keys %i[alt f8]
rce.send_keys(:alt, :f8)
expect(keyboard_shortcut_modal).to be_displayed
end
@ -1184,7 +1171,7 @@ describe "RCE next tests", ignore_js_errors: true do
visit_front_page_edit(@course)
rce = f(".tox-edit-area__iframe")
expect(f(".tox-menubar")).to be_displayed # always show menubar now
rce.send_keys %i[alt f9]
rce.send_keys(:alt, :f9)
expect(f(".tox-menubar")).to be_displayed
expect(fj('.tox-menubar button:contains("Edit")')).to eq(driver.switch_to.active_element)
@ -1193,7 +1180,7 @@ describe "RCE next tests", ignore_js_errors: true do
it "focuses the toolbar with alt-f10" do
visit_front_page_edit(@course)
rce = f(".tox-edit-area__iframe")
rce.send_keys %i[alt f10]
rce.send_keys(:alt, :f10)
expect(fj('.tox-toolbar__primary button:contains("12pt")')).to eq(
driver.switch_to.active_element

View File

@ -339,8 +339,7 @@ module CustomSeleniumActions
tinymce_element = f("body")
until tinymce_element.text.empty?
tinymce_element.click
tinymce_element.send_keys(Array.new(100, :backspace))
tinymce_element = f("body")
tinymce_element.send_keys([:control, "a"], :backspace)
end
end
else
@ -515,14 +514,6 @@ module CustomSeleniumActions
keys = value.to_s.empty? ? [:backspace] : []
keys << value
el.send_keys(*keys)
count = 0
until el["value"] == value.to_s
break if count > 1
count += 1
driver.execute_script("arguments[0].select();", el)
el.send_keys(*keys)
end
end
el.send_keys(:tab) if options[:tab_out]

View File

@ -22,20 +22,15 @@ require "selenium-webdriver"
module Selenium
module WebDriver
module Remote
module W3C
class Bridge
COMMANDS = remove_const(:COMMANDS).dup
COMMANDS[:get_log] = [:post, "session/:session_id/log"]
COMMANDS.freeze
class Bridge
def log(type)
command(:get_log, "session/:session_id/log", :post)
data = execute :get_log, {}, { type: type.to_s }
def log(type)
data = execute :get_log, {}, { type: type.to_s }
Array(data).map do |l|
LogEntry.new l.fetch("level", "UNKNOWN"), l.fetch("timestamp"), l.fetch("message")
rescue KeyError
next
end
Array(data).map do |l|
LogEntry.new l.fetch("level", "UNKNOWN"), l.fetch("timestamp"), l.fetch("message")
rescue KeyError
next
end
end
end

View File

@ -23,20 +23,6 @@ require_relative "common_helper_methods/custom_alert_actions"
require_relative "common_helper_methods/custom_screen_actions"
require_relative "patches/selenium/webdriver/remote/w3c/bridge"
# WebDriver uses port 7054 (the "locking port") as a mutex to ensure
# that we don't launch two Firefox instances at the same time. Each
# new instance you create will wait for the mutex before starting
# the browser, then release it as soon as the browser is open.
#
# The default port mutex wait timeout is 45 seconds.
# Bump it to 90 seconds as a stopgap for the recent flood of:
# `unable to bind to locking port 7054 within 45 seconds`
#
# TODO: Investigate why it's taking so long to launch Firefox, or
# what process is hogging port 7054.
Selenium::WebDriver::Firefox::Launcher.send :remove_const, :SOCKET_LOCK_TIMEOUT
Selenium::WebDriver::Firefox::Launcher::SOCKET_LOCK_TIMEOUT = 90
module SeleniumDriverSetup
CONFIG = ConfigFile.load("selenium") || {}.freeze
SECONDS_UNTIL_GIVING_UP = 10
@ -275,9 +261,8 @@ module SeleniumDriverSetup
# by modifying 'chromedriver_version: <version>' for the version you want.
# otherwise this will use the default version matching what is used in docker.
Webdrivers::Chromedriver.required_version = CONFIG[:chromedriver_version]
chrome_options = Selenium::WebDriver::Chrome::Options.new
Selenium::WebDriver.for :chrome, desired_capabilities: desired_capabilities, options: chrome_options
Selenium::WebDriver.for :chrome, capabilities: desired_capabilities
end
def ruby_safari_driver
@ -287,16 +272,16 @@ module SeleniumDriverSetup
def ruby_edge_driver
puts "Thread: provisioning local edge driver"
edge_options = Selenium::WebDriver::Edge::Options.new
Selenium::WebDriver.for :edge, desired_capabilities: desired_capabilities, options: edge_options
Selenium::WebDriver.for :edge, capabilities: desired_capabilities
end
def selenium_remote_driver
puts "Thread: provisioning remote #{browser} driver"
puts "Selenium_Url: #{selenium_url}"
driver = Selenium::WebDriver.for(
:remote,
url: selenium_url,
desired_capabilities: desired_capabilities
capabilities: desired_capabilities
)
driver.file_detector = lambda do |args|
@ -311,37 +296,36 @@ module SeleniumDriverSetup
def desired_capabilities
case browser
when :firefox
caps = Selenium::WebDriver::Remote::Capabilities.firefox
options = Selenium::WebDriver::Options.firefox
options.log_level = :debug
when :chrome
caps = Selenium::WebDriver::Remote::Capabilities.chrome
caps["goog:chromeOptions"] = {
args: %w[disable-dev-shm-usage no-sandbox start-maximized]
}
caps["goog:loggingPrefs"] = {
options = Selenium::WebDriver::Options.chrome
options.add_argument("no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-dev-shm-usage")
options.logging_prefs = {
browser: "ALL"
}
# put `auto_open_devtools: true` in your selenium.yml if you want to have
# the chrome dev tools open by default by selenium
if CONFIG[:auto_open_devtools]
caps["goog:chromeOptions"][:args].append("auto-open-devtools-for-tabs")
options.add_argument("auto-open-devtools-for-tabs")
end
# put `headless: true` and `window_size: "<x>,<y>"` in your selenium.yml
# if you want to run against headless chrome
if CONFIG[:headless]
caps["goog:chromeOptions"][:args].append("headless")
options.add_argument("headless")
end
if CONFIG[:window_size].present?
caps["goog:chromeOptions"][:args].append("window-size=#{CONFIG[:window_size]}")
end
caps["unexpectedAlertBehaviour"] = "ignore"
when :edge
caps = Selenium::WebDriver::Remote::Capabilities.edge
options = Selenium::WebDriver::Options.edge
options.add_argument("disable-dev-shm-usage")
when :safari
# TODO: options for safari driver
else
raise "unsupported browser #{browser}"
end
caps
options.unhandled_prompt_behavior = "ignore"
options
end
def selenium_url
@ -362,24 +346,7 @@ module SeleniumDriverSetup
def ruby_firefox_driver
puts "Thread: provisioning local firefox driver"
Selenium::WebDriver.for(:firefox,
profile: firefox_profile,
desired_capabilities: desired_capabilities)
end
def firefox_profile
if CONFIG[:firefox_path].present?
Selenium::WebDriver::Firefox::Binary.path = (CONFIG[:firefox_path]).to_s
end
profile = Selenium::WebDriver::Firefox::Profile.new
profile.add_extension Rails.root.join("spec/selenium/test_setup/JSErrorCollector.xpi")
profile.log_file = "/dev/stdout"
# firefox randomly reloads if/when it decides to download the OpenH264 codec, so don't let it
profile["media.gmp-manager.url"] = ""
if CONFIG[:firefox_profile].present?
profile = Selenium::WebDriver::Firefox::Profile.from_name(CONFIG[:firefox_profile])
end
profile
capabilities: desired_capabilities)
end
def_delegator :driver_capabilities, :browser_name
@ -517,18 +484,6 @@ module SeleniumDriverSetup
end
end
# get some extra verbose logging from firefox for when things go wrong
Selenium::WebDriver::Firefox::Binary.class_eval do
def execute(*extra_args)
args = [self.class.path, "-no-remote"] + extra_args
SeleniumDriverSetup.browser_process = @process = ChildProcess.build(*args)
SeleniumDriverSetup.browser_log = @process.io.stdout = @process.io.stderr = Tempfile.new("firefox")
$DEBUG = true
@process.start
$DEBUG = nil
end
end
# make Wait play nicely with Timecop
module Selenium::WebDriver::Wait::Time
def self.now

View File

@ -256,4 +256,4 @@ Selenium::WebDriver::Element.prepend(SeleniumExtensions::FinderWaiting)
Selenium::WebDriver::Element.prepend(SeleniumExtensions::UnexpectedPageReloadProtectionElement)
Selenium::WebDriver::Driver.prepend(SeleniumExtensions::PreventEarlyInteraction)
Selenium::WebDriver::Driver.prepend(SeleniumExtensions::FinderWaiting)
Selenium::WebDriver::W3CActionBuilder.prepend(SeleniumExtensions::UnexpectedPageReloadProtectionActionBuilder)
Selenium::WebDriver::ActionBuilder.prepend(SeleniumExtensions::UnexpectedPageReloadProtectionActionBuilder)