diff --git a/java/src/org/openqa/selenium/bidi/module/Input.java b/java/src/org/openqa/selenium/bidi/module/Input.java index e8013c7383bf7..c9a901bbb7fe5 100644 --- a/java/src/org/openqa/selenium/bidi/module/Input.java +++ b/java/src/org/openqa/selenium/bidi/module/Input.java @@ -19,6 +19,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -27,6 +28,7 @@ import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.bidi.Command; import org.openqa.selenium.bidi.HasBiDi; +import org.openqa.selenium.bidi.script.RemoteReference; import org.openqa.selenium.interactions.Sequence; public class Input { @@ -87,4 +89,32 @@ public void perform(String browsingContext, Collection actions) { public void release(String browsingContext) { bidi.send(new Command<>("input.releaseActions", Map.of("context", browsingContext))); } + + public void setFiles(String browsingContext, RemoteReference element, List files) { + bidi.send( + new Command<>( + "input.setFiles", + Map.of("context", browsingContext, "element", element.toJson(), "files", files))); + } + + public void setFiles(String browsingContext, String elementId, List files) { + bidi.send( + new Command<>( + "input.setFiles", + Map.of( + "context", + browsingContext, + "element", + new RemoteReference(RemoteReference.Type.SHARED_ID, elementId).toJson(), + "files", + files))); + } + + public void setFiles(String browsingContext, RemoteReference element, String file) { + setFiles(browsingContext, element, Collections.singletonList(file)); + } + + public void setFiles(String browsingContext, String elementId, String file) { + setFiles(browsingContext, elementId, Collections.singletonList(file)); + } } diff --git a/java/test/org/openqa/selenium/bidi/input/SetFilesCommandTest.java b/java/test/org/openqa/selenium/bidi/input/SetFilesCommandTest.java new file mode 100644 index 0000000000000..ccdb2dfde05e1 --- /dev/null +++ b/java/test/org/openqa/selenium/bidi/input/SetFilesCommandTest.java @@ -0,0 +1,143 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.openqa.selenium.bidi.input; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openqa.selenium.testing.drivers.Browser.EDGE; +import static org.openqa.selenium.testing.drivers.Browser.FIREFOX; +import static org.openqa.selenium.testing.drivers.Browser.IE; +import static org.openqa.selenium.testing.drivers.Browser.SAFARI; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.bidi.module.Input; +import org.openqa.selenium.bidi.script.RemoteReference; +import org.openqa.selenium.environment.webserver.AppServer; +import org.openqa.selenium.environment.webserver.NettyAppServer; +import org.openqa.selenium.remote.RemoteWebElement; +import org.openqa.selenium.testing.JupiterTestBase; +import org.openqa.selenium.testing.NotYetImplemented; + +public class SetFilesCommandTest extends JupiterTestBase { + private Input input; + + private String windowHandle; + + private AppServer server; + + @BeforeEach + public void setUp() { + windowHandle = driver.getWindowHandle(); + input = new Input(driver); + server = new NettyAppServer(); + server.start(); + } + + @Test + @NotYetImplemented(SAFARI) + @NotYetImplemented(IE) + @NotYetImplemented(EDGE) + @NotYetImplemented(FIREFOX) + void canSetFiles() throws IOException { + driver.get(pages.formPage); + WebElement uploadElement = driver.findElement(By.id("upload")); + assertThat(uploadElement.getAttribute("value")).isEmpty(); + + File file = File.createTempFile("test", "txt"); + file.deleteOnExit(); + + List paths = new ArrayList<>(); + paths.add(file.getAbsolutePath()); + + input.setFiles( + windowHandle, + new RemoteReference( + RemoteReference.Type.SHARED_ID, ((RemoteWebElement) uploadElement).getId()), + paths); + + assertThat(uploadElement.getAttribute("value")).endsWith(file.getName()); + } + + @Test + @NotYetImplemented(SAFARI) + @NotYetImplemented(IE) + @NotYetImplemented(EDGE) + @NotYetImplemented(FIREFOX) + public void canSetFilesWithElementId() throws IOException { + driver.get(pages.formPage); + WebElement uploadElement = driver.findElement(By.id("upload")); + assertThat(uploadElement.getAttribute("value")).isEmpty(); + + File file = File.createTempFile("test", "txt"); + file.deleteOnExit(); + + List paths = new ArrayList<>(); + paths.add(file.getAbsolutePath()); + + input.setFiles(windowHandle, ((RemoteWebElement) uploadElement).getId(), paths); + + assertThat(uploadElement.getAttribute("value")).endsWith(file.getName()); + } + + @Test + @NotYetImplemented(SAFARI) + @NotYetImplemented(IE) + @NotYetImplemented(EDGE) + @NotYetImplemented(FIREFOX) + void canSetFile() throws IOException { + driver.get(pages.formPage); + WebElement uploadElement = driver.findElement(By.id("upload")); + assertThat(uploadElement.getAttribute("value")).isEmpty(); + + File file = File.createTempFile("test", "txt"); + file.deleteOnExit(); + + input.setFiles( + windowHandle, + new RemoteReference( + RemoteReference.Type.SHARED_ID, ((RemoteWebElement) uploadElement).getId()), + file.getAbsolutePath()); + + assertThat(uploadElement.getAttribute("value")).endsWith(file.getName()); + } + + @Test + @NotYetImplemented(SAFARI) + @NotYetImplemented(IE) + @NotYetImplemented(EDGE) + @NotYetImplemented(FIREFOX) + void canSetFileWithElementId() throws IOException { + driver.get(pages.formPage); + WebElement uploadElement = driver.findElement(By.id("upload")); + assertThat(uploadElement.getAttribute("value")).isEmpty(); + + File file = File.createTempFile("test", "txt"); + file.deleteOnExit(); + + input.setFiles( + windowHandle, ((RemoteWebElement) uploadElement).getId(), file.getAbsolutePath()); + + assertThat(uploadElement.getAttribute("value")).endsWith(file.getName()); + } +} diff --git a/javascript/node/selenium-webdriver/bidi/input.js b/javascript/node/selenium-webdriver/bidi/input.js index 4ac337e650a49..e3bb0f27a7947 100644 --- a/javascript/node/selenium-webdriver/bidi/input.js +++ b/javascript/node/selenium-webdriver/bidi/input.js @@ -18,6 +18,7 @@ // type: module added to package.json // import { WebElement } from '../lib/webdriver' const { WebElement } = require('../lib/webdriver') +const { RemoteReferenceType, ReferenceValue } = require('./protocolValue') class Input { constructor(driver) { @@ -57,6 +58,26 @@ class Input { } return await this.bidi.send(command) } + + async setFiles(browsingContextId, element, files) { + + if (typeof element !== 'string' && !(element instanceof ReferenceValue)) { + throw Error(`Pass in a WebElement id as a string or a ReferenceValue. Received: ${element}`) + } + + const command = { + method: 'input.setFiles', + params: { + context: browsingContextId, + element: + typeof element === 'string' + ? new ReferenceValue(RemoteReferenceType.SHARED_ID, element).asMap() + : element.asMap(), + files: typeof files === 'string' ? [files] : files, + }, + } + await this.bidi.send(command) + } } async function updateActions(actions) { diff --git a/javascript/node/selenium-webdriver/bidi/protocolValue.js b/javascript/node/selenium-webdriver/bidi/protocolValue.js index d4faf583e160a..825df9fc7fbc0 100644 --- a/javascript/node/selenium-webdriver/bidi/protocolValue.js +++ b/javascript/node/selenium-webdriver/bidi/protocolValue.js @@ -160,23 +160,27 @@ class RemoteValue { } class ReferenceValue { + #handle + #sharedId constructor(handle, sharedId) { if (handle === RemoteReferenceType.HANDLE) { - this.handle = sharedId + this.#handle = sharedId + } else if (handle === RemoteReferenceType.SHARED_ID) { + this.#sharedId = sharedId } else { - this.handle = handle - this.sharedId = sharedId + this.#handle = handle + this.#sharedId = sharedId } } asMap() { const toReturn = {} - if (this.handle != null) { - toReturn[RemoteReferenceType.HANDLE] = this.handle + if (this.#handle != null) { + toReturn[RemoteReferenceType.HANDLE] = this.#handle } - if (this.sharedId != null) { - toReturn[RemoteReferenceType.SHARED_ID] = this.sharedId + if (this.#sharedId != null) { + toReturn[RemoteReferenceType.SHARED_ID] = this.#sharedId } return toReturn diff --git a/javascript/node/selenium-webdriver/test/bidi/setFiles_command_test.js b/javascript/node/selenium-webdriver/test/bidi/setFiles_command_test.js new file mode 100644 index 0000000000000..f0a97e98f888b --- /dev/null +++ b/javascript/node/selenium-webdriver/test/bidi/setFiles_command_test.js @@ -0,0 +1,96 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict' + +const assert = require('assert') +require('../../lib/test/fileserver') +const firefox = require('../../firefox') +const { Pages, suite } = require('../../lib/test') +const { Browser, By } = require('../..') +const Input = require('../../bidi/input') +const io = require('../../io') +const fs = require('node:fs') +const { ReferenceValue, RemoteReferenceType } = require('../../bidi/protocolValue') + +suite( + function (env) { + describe('Input Set Files', function () { + const FILE_HTML = '
' + 'Hello' + '
' + let driver + + let _fp + before(function () { + return (_fp = io.tmpFile().then(function (fp) { + fs.writeFileSync(fp, FILE_HTML) + return fp + })) + }) + + beforeEach(async function () { + driver = await env.builder().setFirefoxOptions(new firefox.Options().enableBidi()).build() + }) + + afterEach(function () { + return driver.quit() + }) + + xit('can set files', async function () { + const browsingContextId = await driver.getWindowHandle() + const input = await Input(driver) + await driver.get(Pages.formPage) + + const filePath = await io.tmpFile().then(function(fp) { + fs.writeFileSync(fp, FILE_HTML) + return fp + }) + + const webElement = await driver.findElement(By.id('upload')) + + assert.strictEqual(await webElement.getAttribute('value'), '') + + const webElementId = await webElement.getId() + + await input.setFiles(browsingContextId, new ReferenceValue(RemoteReferenceType.SHARED_ID, webElementId), [filePath]) + + assert.notEqual(await webElement.getAttribute('value'), '') + }) + + xit('can set files with element id', async function () { + const browsingContextId = await driver.getWindowHandle() + const input = await Input(driver) + await driver.get(Pages.formPage) + + const filePath = await io.tmpFile().then(function(fp) { + fs.writeFileSync(fp, FILE_HTML) + return fp + }) + + const webElement = await driver.findElement(By.id('upload')) + + assert.strictEqual(await webElement.getAttribute('value'), '') + + const webElementId = await webElement.getId() + + await input.setFiles(browsingContextId, webElementId, filePath) + + assert.notEqual(await webElement.getAttribute('value'), '') + }) + }) + }, + { browsers: [Browser.FIREFOX] }, +)