Skip to content

Commit

Permalink
We no longer wait for <video> and <audio> elements to load their …
Browse files Browse the repository at this point in the history
…metadata
  • Loading branch information
triskweline committed Jun 21, 2024
1 parent 9728a3b commit 8650356
Show file tree
Hide file tree
Showing 4 changed files with 2 additions and 205 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
- We now only wait for `<script>` elements with a JavaScript type
- We only wait for `<iframe>` elements with a `[src]` attribute
- We no longer wait for `<iframe>` elements that have already finished loading
- We no longer wait for `<video>` and `<audio>` elements to load their metadata. This did not work consistently, and would sometimes cause capybara-lockstep to wait indefinitely.


# 2.2.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ When capybara-lockstep synchronizes it will:
- wait for any pending AJAX requests to finish and their callbacks to be called.
- wait for dynamically inserted `<script>`s to load (e.g. from [dynamic imports](https://webpack.js.org/guides/code-splitting/#dynamic-imports) or Analytics snippets).
- wait for dynamically inserted `<img>` or `<iframe>` elements to load (ignoring [lazy-loaded](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#lazy) elements).
- wait for dynamically inserted `<audio>` and `<video>` elements to load their [metadata](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event)

In summary, Capybara can no longer observe or interact with the page while HTTP requests are in flight.
This covers most async work that causes flaky tests.
Expand All @@ -86,6 +85,7 @@ Async work not synchronized by capybara-lockstep includes:
- Websocket connections
- Service workers
- Work scheduled via `setTimeout()` or `setInterval()`.
- `<audio>` and `<video>` elements

You can configure capybara-lockstep to [wait for additional async work](#signaling-asynchronous-work).

Expand Down
12 changes: 0 additions & 12 deletions lib/capybara-lockstep/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,22 +210,11 @@ window.CapybaraLockstep = (function() {
!!element.querySelector('source [src*="data:"], source [srcset*="data:"]')
}

function isTrackableMediaElement(element) {
return element.matches('audio, video') &&
element.readyState === 0 && // no metadata known
!hasDataSource(element) &&
element.getAttribute('preload') !== 'none'
}

function trackRemoteElement(element, condition, workTag) {
trackLoadingElement(element, condition, workTag, 'load', 'error')

}

function trackMediaElement(element, condition, workTag) {
trackLoadingElement(element, condition, workTag, 'loadedmetadata', 'error')
}

function trackLoadingElement(element, condition, workTag, loadEvent, errorEvent) {
if (!condition(element)) {
return
Expand Down Expand Up @@ -275,7 +264,6 @@ window.CapybaraLockstep = (function() {
trackRemoteElement(addedNode, isRemoteScript, 'Script')
trackRemoteElement(addedNode, isTrackableImage, 'Image')
trackRemoteElement(addedNode, isTrackableIFrame, 'Inline frame')
trackMediaElement(addedNode, isTrackableMediaElement, 'Media element')
}
})
})
Expand Down
192 changes: 0 additions & 192 deletions spec/features/synchronization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,198 +215,6 @@

end

describe 'dynamically inserted video elements' do

it 'waits until the video has loaded metadata (without playing)' do
App.start_html = <<~HTML
<a href="#" onclick="
let video = document.createElement('video');
video.src = '/next';
document.body.append(video);
">label</a>
HTML

wall = Wall.new
App.next_action = -> do
wall.block
send_file_sync('spec/fixtures/video.mp4', 'video/mp4')
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
expect(command).to run_into_wall(wall)

wall.release

wait(0.5.seconds).for { command }.to be_finished

expect('video').to be_media_element_with_metadata
end

it 'waits until the video has failed to load metadata' do
App.start_html = <<~HTML
<a href="#" onclick="
let video = document.createElement('video');
video.src = '/next';
document.body.append(video);
">label</a>
HTML

wall = Wall.new
App.next_action = -> do
wall.block
halt 404
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
expect(command).to run_into_wall(wall)

wall.release

wait(0.5.seconds).for { command }.to be_finished
end

it 'does not wait forever for a video with a data: source' do
App.start_html = <<~HTML
<a href="#" onclick="
let video = document.createElement('video');
video.src = `data:video/mp4;base64,#{Base64.encode64(File.read('spec/fixtures/video.mp4')).gsub("\n", '')}`;
document.body.append(video);
">label</a>
HTML

visit '/start'
command = ObservableCommand.new { page.find('a').click }
command.execute
wait(0.1.seconds).for { command }.to be_finished
expect('video').to be_media_element_with_metadata
end

it 'does not wait for a video with a [preload=none] attribute' do
App.start_html = <<~HTML
<a href="#" onclick="
let video = document.createElement('video');
video.src = '/next';
video.setAttribute('preload', 'none');
document.body.append(video);
">label</a>
HTML

server_spy = double('server action', reached: nil)

App.next_action = -> do
server_spy.reached
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
command.execute
wait(0.1.seconds).for { command }.to be_finished

expect(server_spy).to_not have_received(:reached)

expect('video').to have_ready_state(0)
end

end

describe 'dynamically inserted audio elements' do

it 'waits until the audio has loaded metadata (without playing)' do
App.start_html = <<~HTML
<a href="#" onclick="
let audio = document.createElement('audio');
audio.controls = true;
audio.src = '/next';
document.body.append(audio);
">label</a>
HTML

App.next_action = -> do
send_file_sync('spec/fixtures/audio.mp3', 'audio/mpeg')
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
command.execute

wait(0.5.seconds).for { command }.to be_finished

expect('audio').to be_media_element_with_metadata
end

it 'waits until the audio has failed to load metadata' do
App.start_html = <<~HTML
<a href="#" onclick="
let audio = document.createElement('audio');
audio.controls = true;
audio.src = '/next';
document.body.append(audio);
">label</a>
HTML

wall = Wall.new
App.next_action = -> do
wall.block
halt 404
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
expect(command).to run_into_wall(wall)

wall.release

wait(0.5.seconds).for { command }.to be_finished
end

it 'does not wait forever for a audio with a data: source' do
App.start_html = <<~HTML
<a href="#" onclick="
let audio = document.createElement('audio');
audio.controls = true;
audio.src = `data:audio/mpeg;base64,#{Base64.encode64(File.read('spec/fixtures/audio.mp3')).gsub("\n", '')}`;
document.body.append(audio);
">label</a>
HTML

visit '/start'
command = ObservableCommand.new { page.find('a').click }
command.execute
wait(0.1.seconds).for { command }.to be_finished
expect('audio').to be_media_element_with_metadata
end

it 'does not wait for a audio with a [preload=none] attribute' do
App.start_html = <<~HTML
<a href="#" onclick="
let audio = document.createElement('audio');
audio.controls = true;
audio.src = '/next';
audio.setAttribute('preload', 'none');
document.body.append(audio);
">label</a>
HTML

server_spy = double('server action', reached: nil)

App.next_action = -> do
server_spy.reached
end

visit '/start'
command = ObservableCommand.new { page.find('a').click }
command.execute
wait(0.1.seconds).for { command }.to be_finished

expect(server_spy).to_not have_received(:reached)

expect('audio').to have_ready_state(0)
end

end

describe 'dynamically loaded scripts' do

it 'waits until a <script> has loaded' do
Expand Down

0 comments on commit 8650356

Please sign in to comment.