diff --git a/src/element.ts b/src/element.ts index 7d8b31d4..628319db 100644 --- a/src/element.ts +++ b/src/element.ts @@ -54,6 +54,77 @@ export class Element { this.#protocol = protocol; } + /** + * Sets a file for a file input + * + * @param path - The remote path of the file to attach + * + * @example + * ```js + * import { resolve } from "https://deno.land/std@0.136.0/path/mod.ts"; + * const fileInput = await page.querySelector("input[type='file']"); + * await fileInput.file(resolve("./logo.png")); + * ``` + */ + public async file(path: string): Promise { + return await this.files(path); + } + + /** + * Sets many files for a file input + * + * @param files - The list of remote files to attach + * + * @example + * ```js + * import { resolve } from "https://deno.land/std@0.136.0/path/mod.ts"; + * const fileInput = await page.querySelector("input[type='file']"); + * await fileInput.files(resolve("./logo.png")); + * ``` + */ + public async files(...files: string[]) { + if (files.length > 1) { + const isMultiple = await this.#page.evaluate( + `${this.#method}('${this.#selector}').hasAttribute('multiple')`, + ); + if (!isMultiple) { + throw new Error( + "Trying to set files on a file input without the 'multiple' attribute", + ); + } + } + + const name = await this.#page.evaluate( + `${this.#method}('${this.#selector}').nodeName`, + ); + if (name !== "INPUT") { + throw new Error("Trying to set a file on an element that isnt an input"); + } + const type = await this.#page.evaluate( + `${this.#method}('${this.#selector}').type`, + ); + if (type !== "file") { + throw new Error( + 'Trying to set a file on an input that is not of type "file"', + ); + } + + const { node } = await this.#protocol.send< + ProtocolTypes.DOM.DescribeNodeRequest, + ProtocolTypes.DOM.DescribeNodeResponse + >("DOM.describeNode", { + objectId: this.#objectId, + }); + await this.#protocol.send( + "DOM.setFileInputFiles", + { + files: files, + objectId: this.#objectId, + backendNodeId: node.backendNodeId, + }, + ); + } + /** * Get the value of this element, or set the value * diff --git a/tests/deps.ts b/tests/deps.ts index 0113ce55..13a68001 100644 --- a/tests/deps.ts +++ b/tests/deps.ts @@ -1,2 +1,3 @@ export * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; +export { resolve } from "https://deno.land/std@0.136.0/path/mod.ts"; export { delay } from "https://deno.land/std@0.126.0/async/delay.ts"; diff --git a/tests/server.ts b/tests/server.ts index 39ca166d..57ecac94 100644 --- a/tests/server.ts +++ b/tests/server.ts @@ -23,6 +23,19 @@ class PopupsResource extends Drash.Resource { } } +class FileInputResource extends Drash.Resource { + public paths = ["/file-input"]; + + public GET(_r: Drash.Request, res: Drash.Response) { + return res.html(` +

+ + + + `); + } +} + class WaitForRequestsResource extends Drash.Resource { public paths = ["/wait-for-requests"]; @@ -55,6 +68,7 @@ export const server = new Drash.Server({ JSResource, PopupsResource, WaitForRequestsResource, + FileInputResource, ], protocol: "http", port: 1447, diff --git a/tests/unit/element_test.ts b/tests/unit/element_test.ts index 97986ee4..7092008d 100644 --- a/tests/unit/element_test.ts +++ b/tests/unit/element_test.ts @@ -3,6 +3,8 @@ import { assertEquals } from "../../deps.ts"; import { browserList } from "../browser_list.ts"; const ScreenshotsFolder = "./Screenshots"; import { existsSync } from "../../src/utility.ts"; +import { server } from "../server.ts"; +import { resolve } from "../deps.ts"; for (const browserItem of browserList) { Deno.test(browserItem.name, async (t) => { @@ -53,49 +55,28 @@ for (const browserItem of browserList) { assertEquals(page1Location, "https://drash.land/"); assertEquals(page2location, "https://github.com/drashland"); }); + }); - await t.step("takeScreenshot()", async (t) => { - await t.step( - "Takes Screenshot of only the element passed as selector and also quality(only if the image is jpeg)", - async () => { - try { - Deno.removeSync(ScreenshotsFolder, { - recursive: true, - }); - } catch (_e) { - // if doesnt exist, no problamo - } - const { browser, page } = await buildFor(browserItem.name); - await page.location("https://drash.land"); - const img = await page.querySelector("img"); - Deno.mkdirSync(ScreenshotsFolder); - const fileName = await img.takeScreenshot(ScreenshotsFolder, { - quality: 50, - }); - await browser.close(); - const exists = existsSync(fileName); + await t.step("takeScreenshot()", async (t) => { + await t.step( + "Takes Screenshot of only the element passed as selector and also quality(only if the image is jpeg)", + async () => { + try { Deno.removeSync(ScreenshotsFolder, { recursive: true, }); - assertEquals( - exists, - true, - ); - }, - ); - - await t.step("Saves Screenshot with all options provided", async () => { + } catch (_e) { + // if doesnt exist, no problamo + } const { browser, page } = await buildFor(browserItem.name); - await page.location("https://chromestatus.com"); - const h3 = await page.querySelector("h3"); + await page.location("https://drash.land"); + const img = await page.querySelector("img"); Deno.mkdirSync(ScreenshotsFolder); - const filename = await h3.takeScreenshot(ScreenshotsFolder, { - fileName: "AllOpts", - format: "jpeg", - quality: 100, + const fileName = await img.takeScreenshot(ScreenshotsFolder, { + quality: 50, }); await browser.close(); - const exists = existsSync(filename); + const exists = existsSync(fileName); Deno.removeSync(ScreenshotsFolder, { recursive: true, }); @@ -103,55 +84,216 @@ for (const browserItem of browserList) { exists, true, ); - }); - }); + }, + ); - await t.step("value", async (t) => { - await t.step( - "It should get the value for the given input element", - async () => { - const { browser, page } = await buildFor(browserItem.name); - await page.location("https://chromestatus.com"); - const elem = await page.querySelector( - 'input[placeholder="Filter"]', - ); - await elem.value("hello world"); - const val = await elem.value(); - assertEquals(val, "hello world"); - await browser.close(); - }, - ); - await t.step( - "Should return empty when element is not an input element", - async () => { - const { browser, page } = await buildFor(browserItem.name); - await page.location("https://chromestatus.com"); - let errMsg = ""; - const elem = await page.querySelector("div"); - try { - await elem.value; - } catch (e) { - errMsg = e.message; - } - await browser.close(); - assertEquals( - errMsg, - "", - ); - }, + await t.step("Saves Screenshot with all options provided", async () => { + const { browser, page } = await buildFor(browserItem.name); + await page.location("https://chromestatus.com"); + const h3 = await page.querySelector("h3"); + Deno.mkdirSync(ScreenshotsFolder); + const filename = await h3.takeScreenshot(ScreenshotsFolder, { + fileName: "AllOpts", + format: "jpeg", + quality: 100, + }); + await browser.close(); + const exists = existsSync(filename); + Deno.removeSync(ScreenshotsFolder, { + recursive: true, + }); + assertEquals( + exists, + true, ); }); + }); - await t.step("value()", async (t) => { - await t.step("It should set the value of the element", async () => { + await t.step("value", async (t) => { + await t.step( + "It should get the value for the given input element", + async () => { const { browser, page } = await buildFor(browserItem.name); await page.location("https://chromestatus.com"); - const elem = await page.querySelector('input[placeholder="Filter"]'); + const elem = await page.querySelector( + 'input[placeholder="Filter"]', + ); await elem.value("hello world"); const val = await elem.value(); - await browser.close(); assertEquals(val, "hello world"); - }); + await browser.close(); + }, + ); + await t.step( + "Should return empty when element is not an input element", + async () => { + const { browser, page } = await buildFor(browserItem.name); + await page.location("https://chromestatus.com"); + let errMsg = ""; + const elem = await page.querySelector("div"); + try { + await elem.value; + } catch (e) { + errMsg = e.message; + } + await browser.close(); + assertEquals( + errMsg, + "", + ); + }, + ); + }); + + await t.step("value()", async (t) => { + await t.step("It should set the value of the element", async () => { + const { browser, page } = await buildFor(browserItem.name); + await page.location("https://chromestatus.com"); + const elem = await page.querySelector('input[placeholder="Filter"]'); + await elem.value("hello world"); + const val = await elem.value(); + await browser.close(); + assertEquals(val, "hello world"); + }); + }); + + await t.step("files()", async (t) => { + await t.step( + "Should throw if multiple files and input isnt multiple", + async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("#single-file"); + let errMsg = ""; + try { + await elem.files("ffff", "hhh"); + } catch (e) { + errMsg = e.message; + } finally { + await server.close(); + await browser.close(); + } + assertEquals( + errMsg, + `Trying to set files on a file input without the 'multiple' attribute`, + ); + }, + ); + await t.step("Should throw if element isnt an input", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("p"); + let errMsg = ""; + try { + await elem.files("ffff"); + } catch (e) { + errMsg = e.message; + } finally { + await server.close(); + await browser.close(); + } + assertEquals( + errMsg, + "Trying to set a file on an element that isnt an input", + ); + }); + await t.step("Should throw if input is not of type file", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("#text"); + let errMsg = ""; + try { + await elem.files("ffff"); + } catch (e) { + errMsg = e.message; + } finally { + await server.close(); + await browser.close(); + } + assertEquals( + errMsg, + 'Trying to set a file on an input that is not of type "file"', + ); + }); + await t.step("Should successfully upload files", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("#multiple-file"); + try { + await elem.files(resolve("./README.md"), resolve("./tsconfig.json")); + const files = JSON.parse( + await page.evaluate( + `JSON.stringify(document.querySelector('#multiple-file').files)`, + ), + ); + assertEquals(Object.keys(files).length, 2); + } finally { + await server.close(); + await browser.close(); + } + }); + }); + + await t.step("file()", async (t) => { + await t.step("Should throw if element isnt an input", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("p"); + let errMsg = ""; + try { + await elem.file("ffff"); + } catch (e) { + errMsg = e.message; + } finally { + await server.close(); + await browser.close(); + } + assertEquals( + errMsg, + "Trying to set a file on an element that isnt an input", + ); + }); + await t.step("Should throw if input is not of type file", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("#text"); + let errMsg = ""; + try { + await elem.file("ffff"); + } catch (e) { + errMsg = e.message; + } finally { + await server.close(); + await browser.close(); + } + assertEquals( + errMsg, + 'Trying to set a file on an input that is not of type "file"', + ); + }); + await t.step("Should successfully upload files", async () => { + server.run(); + const { browser, page } = await buildFor(browserItem.name); + await page.location(server.address + "/file-input"); + const elem = await page.querySelector("#single-file"); + try { + await elem.file(resolve("./README.md")); + const files = JSON.parse( + await page.evaluate( + `JSON.stringify(document.querySelector('#single-file').files)`, + ), + ); + assertEquals(Object.keys(files).length, 1); + } finally { + await server.close(); + await browser.close(); + } }); });