Skip to content

Commit 019c9e5

Browse files
committed
Add details about handling redirects.
1 parent 5d07616 commit 019c9e5

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

guides/navigation-timing/readme.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,33 @@ If any navigation operation is triggered during steps 1-4, it **interrupts** thi
2525
- The intended navigation never finishes.
2626
- Your test ends up in an unexpected state.
2727

28+
## The Redirect Race Condition
29+
30+
A particularly common variant of this race condition occurs with **HTTP redirects** (302, 301, etc.). When a form submission or other action triggers a redirect:
31+
32+
1. **Form Submission**: Browser sends POST request to `/submit`.
33+
2. **Server Response**: Server returns `302 Found` with `Location: /success` header.
34+
3. **Redirect Processing**: Browser receives the 302 response (usually with empty body).
35+
4. **Follow Redirect**: Browser automatically navigates to `/success`.
36+
5. **Final Page Load**: Success page loads with actual content.
37+
38+
The race condition occurs because element-based waits can execute during step 3, when the browser has received the 302 response but hasn't yet loaded the target page:
39+
40+
```ruby
41+
session.click_button("Submit") # Triggers POST -> 302 redirect
42+
session.find_element(xpath: "//h1") # May execute on empty 302 page!
43+
session.navigate_to("/other-page") # Interrupts redirect to /success
44+
```
45+
46+
This explains why redirect-based workflows (login forms, contact forms, checkout processes) are particularly susceptible to race conditions.
47+
2848
## Problematic Code Examples
2949

3050
### Example 1: Login Form Race Condition
3151

3252
```ruby
3353
# ❌ PROBLEMATIC: May interrupt login before authentication completes
34-
session.click_button("Login") # Triggers form submission
54+
session.click_button("Login") # Triggers form submission.
3555
session.navigate_to("/dashboard") # May interrupt login process!
3656
```
3757

@@ -40,10 +60,19 @@ session.navigate_to("/dashboard") # May interrupt login process!
4060
```ruby
4161
# ❌ PROBLEMATIC: May interrupt form submission
4262
session.fill_in("email", "[email protected]")
43-
session.click_button("Subscribe") # Triggers form submission
63+
session.click_button("Subscribe") # Triggers form submission.
4464
session.navigate_to("/thank-you") # May interrupt subscription action on server!
4565
```
4666

67+
### Example 3: Redirect Race Condition
68+
69+
```ruby
70+
# ❌ PROBLEMATIC: May interrupt redirect before it completes
71+
session.click_button("Submit") # POST -> 302 redirect.
72+
session.find_element(xpath: "//h1") # May find element on 302 page and fail.
73+
session.navigate_to("/dashboard") # Interrupts redirect to success page.
74+
```
75+
4776
## Detection and Mitigation Strategies
4877

4978
⚠️ **Important**: Element-based waits (`find_element`) are **insufficient** for preventing race conditions because navigation can be interrupted before target elements ever appear on the page.
@@ -67,8 +96,7 @@ For critical operations like authentication, wait for server-side effects to com
6796
# ✅ RELIABLE: Wait for authentication cookie
6897
session.click_button("Login")
6998
session.wait_for_navigation do |url, ready_state|
70-
ready_state == "complete" &&
71-
session.cookies.any? {|cookie| cookie['name'] == 'auth_token'}
99+
ready_state == "complete" && session.cookies.any?{|cookie| cookie['name'] == 'auth_token'}
72100
end
73101
session.navigate_to("/dashboard") # Now safe
74102
```
@@ -79,20 +107,20 @@ These approaches are commonly used but **may still allow race conditions**:
79107

80108
#### Element-based Waits
81109

82-
Unfortunately, waiting for specific elements to appear does not always work when navigation operations are in progress. In some cases, the element isn't present before the navigation starts, which can cause hangs. However, sometimes it may work by chance, if the navigation completes before the find element call is invoked. It also appears to depend on which browser is being used, and perhaps even which version of that browser.
110+
Unfortunately, waiting for specific elements to appear does not always work when navigation operations are in progress. This is especially problematic with redirects, where element waits can execute on the intermediate redirect response (which typically has no content) rather than the final destination page.
83111

84112
```ruby
85113
# ❌ UNRELIABLE: Navigation can be interrupted before element appears
86-
session.click_button("Submit")
114+
session.click_button("Submit") # Triggers POST -> 302 redirect
87115

88116
# In principle, wait for the form submission to complete:
89117
session.find_element(xpath: "//h1[text()='Success']")
90118
# However, in reality it may:
91-
# 1. Fail to find element on initial page, or
92-
# 2. Hang if the navigation is still in progress.
93-
# 3. Succeed by chance if navigation has completed sufficiently.
119+
# 1. Execute on the 302 redirect page (empty content) and fail immediately
120+
# 2. Hang if the redirect navigation is still in progress
121+
# 3. Succeed by chance if the redirect has completed sufficiently
94122

95-
# Assuming the previous operation did not hang, this navigation may interrupt the form submission:
123+
# Assuming the previous operation did not hang, this navigation may interrupt the redirect:
96124
session.navigate_to("/next-page")
97125
```
98126

test/async/webdriver/scope/navigation.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,11 @@
8080

8181
session.click_button("Submit")
8282

83-
# Use wait_for_navigation to properly wait for the form submission to complete:
83+
# Use wait_for_navigation to properly wait for the form submission to complete and the redirect to occur:
8484
session.wait_for_navigation do |current_url|
8585
current_url.end_with?("/success")
8686
end
87+
# If you remove the redirect, the above wait_for_navigation is not needed.
8788

8889
# This alone is insufficient, the above wait_for_navigation ensures the page is loaded:
8990
session.find_element_by_xpath("//h1[text()='Form Submitted Successfully']")

0 commit comments

Comments
 (0)