Skip to content

Commit

Permalink
Handle multiple alerts with one promise
Browse files Browse the repository at this point in the history
  • Loading branch information
aaltat committed Oct 6, 2024
1 parent 645b664 commit fea52cc
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 0 deletions.
58 changes: 58 additions & 0 deletions Browser/keywords/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,64 @@ def wait_for_alert(
logger.debug("Not verifying alter text.")
return response.body

@keyword(tags=("Wait", "PageContent"))
def wait_for_alerts(
self,
actions: list[DialogAction],
prompt_inputs: list[Union[None, str]],
texts: list[Union[None, str]],
timeout: Optional[timedelta] = None,
) -> list[str]:
"""Returns a promise to wait for multiple dialog on a page.
Handles each alert/dialog with ``actions`` and optionally verifies the dialogs texts.
Dialog/alert can be any of alert, beforeunload, confirm or prompt.
| =Arguments= | =Description= |
| ``actions`` | List of how to handle the alerts. Can be ``accept`` or ``dismiss``. |
| ``prompt_inputs`` | List of the values to enter into prompt. Only valid if ``action`` argument equals ``accept``. Defaults to empty string. IF input not preset, use None |
| ``texts`` | List of optional text to verify the dialogs text. Use None if text verification should be disabled. |
| ``timeout`` | Optional timeout in Robot Framework time format. |
There must be equal amount of items in ``actions``, ``prompt_inputs`` and ``texts`` lists.
Use None if texts and/or prompt_inputs are not needed.
Example to handle two alerts, first one is accepted and second one is dismissed:
| ${promise} = Promise To Wait For Alerts ["accept", "dismiss"] [None, None] [None, None] 5s
| Click id=alerts
| ${texts} = Wait For ${promise}
[https://forum.robotframework.org/t//4344|Comment >>]
"""
if not len(actions) == len(prompt_inputs) == len(texts):
raise ValueError(
"There was not equal amount of items in actions, prompt_inputs and texts lists. "
f"actions: {len(actions)}, prompt_inputs: {len(prompt_inputs)}, texts: {len(texts)}"
)
lib_default_or_timeout = self.get_timeout(timeout)
alert_actions = Request().AlertActions()
for action, prompt_input in zip(actions, prompt_inputs):
alert_action = Request().AlertAction()
alert_action.alertAction = action.name
alert_action.promptInput = prompt_input or ""
alert_action.timeout = lib_default_or_timeout
alert_actions.items.append(alert_action)
with self.playwright.grpc_channel() as stub:
response = stub.WaitForAlerts(alert_actions)
logger.debug(response.items)
index = 1
for expected_text, received_text in zip(texts, response.items):
if expected_text is None:
index += 1
continue
assert (
expected_text == received_text
), f'Alert index {index} text was: "{received_text}" but it should have been: "{expected_text}"'
index += 1
return response.items

@keyword(tags=("Setter", "PageContent"))
def mouse_button(
self,
Expand Down
68 changes: 68 additions & 0 deletions atest/test/02_Content_Keywords/dialogs.robot
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,71 @@ Wait For Alert Times Out
ELSE
Fail Expected timeout error
END

Handle Multiple Alerts
[Setup] New Page ${DIALOGS_TWO_URL}
${promise} = Promise To
... Wait For Alerts
... ["accept", "dismiss"]
... [None, None]
... ["First alert!", None]
... 5s
Click id=alerts
${texts} = Wait For ${promise}
Should Be Equal ${texts}[0] First alert!
Should Be Equal ${texts}[1] Second alert!
Length Should Be ${texts} 2

Handle Multiple Dialogs With Wrong Number Of Arguments
TRY
Wait For Alerts ["accept"] ["foobar", "Am an alert"] ["Accept", None] 5s
EXCEPT ValueError: There was not equal amount of items in actions, prompt_inputs and texts lists. actions: 1, prompt_inputs: 2, texts: 2
Log Got expected error, all is good.
END

Handle Multiple Dialogs With Wrong Texts
[Setup] Go To ${DIALOGS_TWO_URL}
${promise} = Promise To
... Wait For Alerts
... ["accept", "dismiss"]
... [None, None]
... [None, "This is WRONG"]
... 5s
Click id=alerts
TRY
Wait For ${promise}
EXCEPT Alert index 2 text was: "Second alert!" but it should have been: "This is WRONG"
Log Got expected error, all is good.
END

Handle Conform and Prompt
[Setup] Go To ${DIALOGS_TWO_URL}
${promise} = Promise To
... Wait For Alerts
... ["dismiss", "accept"]
... [None, "I am a prompt"]
... ["First alert accepted?", None]
... 5s
Click id=confirmAndPromt
${texts} = Wait For ${promise}
Should Be Equal ${texts}[0] First alert accepted?
Should Be Equal ${texts}[1] Input in second alert!
Length Should Be ${texts} 2
Get Text id=confirm == First alert declined!
Get Text id=prompt == Second alert input: I am a prompt

Handle Conform and Prompt
[Setup] Go To ${DIALOGS_TWO_URL}
${promise} = Promise To
... Wait For Alerts
... ["accept", "dismiss"]
... [None, "I am a prompt"]
... ["First alert accepted?", None]
... 5s
Click id=confirmAndPromt
${texts} = Wait For ${promise}
Should Be Equal ${texts}[0] First alert accepted?
Should Be Equal ${texts}[1] Input in second alert!
Length Should Be ${texts} 2
Get Text id=confirm == First alert accepted!
Get Text id=prompt == Second alert input: no input
1 change: 1 addition & 0 deletions atest/test/variables.resource
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ${DOG_AND_CAT_URL} = ${ROOT_URL}dogandcat.html
${SHELLGAME_URL} = ${ROOT_URL}shell_game.html
${FORM_URL} = ${ROOT_URL}prefilled_email_form.html
${DIALOGS_URL} = ${ROOT_URL}dialogs.html
${DIALOGS_TWO_URL} = ${ROOT_URL}two_dialogs.html
${ELEMENT_STATE_URL} = ${ROOT_URL}enabled_disabled_fields_form.html
${FRAMES_URL} = ${ROOT_URL}frames/iframes.html
${DEEP_FRAMES_URL} = ${ROOT_URL}frames/deep/a.html
Expand Down
33 changes: 33 additions & 0 deletions node/dynamic-test-app/static/two_dialogs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multiple alerts issue - example</title>
</head>
<body>
<button id="alerts" onclick="showAlerts()">Click to show alerts</button><br><br>
<button id="confirmAndPromt" onclick="showConfirmAndPromt()">Click to confirm and prompt</button>
<p id="confirm"></p>
<p id="prompt"></p>
<script>
function showConfirmAndPromt() {
let trueFalse = confirm("First alert accepted?");
let input = prompt("Input in second alert!");
if (trueFalse) {
document.getElementById("confirm").innerText = "First alert accepted!";
} else {
document.getElementById("confirm").innerText = "First alert declined!";
}
if (input) {
document.getElementById("prompt").innerText = "Second alert input: " + input;
} else {
document.getElementById("prompt").innerText = "Second alert input: no input";
}
}
function showAlerts() {
alert("First alert!");
alert("Second alert!");
}
</script>
</body>
</html>
1 change: 1 addition & 0 deletions node/playwright-wrapper/grpc-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ export class PlaywrightServer implements IPlaywrightServer {
uploadFile = this.wrappingPage(interaction.uploadFile);
handleAlert = this.wrappingPage(interaction.handleAlert);
waitForAlert = this.wrappingPage(interaction.waitForAlert);
waitForAlerts = this.wrappingPage(interaction.waitForAlerts);
mouseMove = this.wrappingPage(interaction.mouseMove);
mouseWheel = this.wrappingPage(interaction.mouseWheel);
mouseButton = this.wrappingPage(interaction.mouseButton);
Expand Down
24 changes: 24 additions & 0 deletions node/playwright-wrapper/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,30 @@ export async function waitForAlert(request: Request.AlertAction, page: Page): Pr
return stringResponse(message, 'Next alert was handeled successfully.');
}

export async function waitForAlerts(request: Request.AlertActions, page: Page): Promise<Response.ListString> {
const response = new Response.ListString();
const alertActions = request.getItemsList();
const alertMessages = [];
for (let index = 0; index < alertActions.length; index++) {
const alertAction = alertActions[index];
const promptInput = alertAction.getPromptinput();
const timeout = alertAction.getTimeout();
const action = alertAction.getAlertaction();
logger.info(`Waiting for alert with action: ${action}, promptInput: "${promptInput}" and timeout: ${timeout}`);
const dialogObject = await page.waitForEvent('dialog', { timeout: timeout });
alertMessages.push(dialogObject.message());
if (action === 'accept' && promptInput) {
dialogObject.accept(promptInput);
} else if (alertAction.getAlertaction() === 'accept') {
dialogObject.accept();
} else {
dialogObject.dismiss();
}
}
response.setItemsList(alertMessages);
return response;
}

export async function mouseButton(request: Request.MouseButtonOptions, page?: Page): Promise<Response.Empty> {
const action = request.getAction() as 'click' | 'up' | 'down';
const params = JSON.parse(request.getJson());
Expand Down
9 changes: 9 additions & 0 deletions protobuf/playwright.proto
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ message Request {
float timeout = 3;
}

message AlertActions {
repeated AlertAction items = 1;
}

message Bool {
bool value = 1;
}
Expand Down Expand Up @@ -317,6 +321,10 @@ message Response {
string body = 2;
}

message ListString {
repeated string items = 1;
}

message Keywords {
string log = 1;
repeated string keywords = 2;
Expand Down Expand Up @@ -484,6 +492,7 @@ service Playwright {

rpc HandleAlert(Request.AlertAction) returns (Response.Empty);
rpc WaitForAlert(Request.AlertAction) returns (Response.String);
rpc WaitForAlerts(Request.AlertActions) returns (Response.ListString);

rpc MouseButton(Request.MouseButtonOptions) returns (Response.Empty);
rpc MouseMove(Request.Json) returns (Response.Empty);
Expand Down

0 comments on commit fea52cc

Please sign in to comment.