From 03757d82b70dc60a261d44eb726654adfeacabad Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Fri, 17 Jun 2022 22:01:20 +0200
Subject: [PATCH] Replace element `id`s with custom attributes for
 Widget-annotations (issue 15056)

We want to avoid adding regular `id`s to Annotation-elements, since that means that they become "linkable" through the URL hash in a way that's not supported/intended. This could end up clashing with "named destinations", and that could easily lead to bugs; see issue 11499 and PR 11503 for some context.

Rather than using `id`s, we'll instead use a *custom* `data-element-id` attribute such that it's still possible to access the Annotation-elements directly.
Unfortunately these changes required updating most of the integration-tests, and to reduce the amount of repeated code a couple of helper functions were added.
---
 src/display/annotation_layer.js     |  30 +-
 test/integration/annotation_spec.js | 105 ++---
 test/integration/scripting_spec.js  | 655 ++++++++++++++--------------
 test/integration/test_utils.js      |  15 +
 web/pdf_scripting_manager.js        |   4 +-
 5 files changed, 425 insertions(+), 384 deletions(-)

diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js
index 1ded7cc00..ba6a61f80 100644
--- a/src/display/annotation_layer.js
+++ b/src/display/annotation_layer.js
@@ -522,7 +522,9 @@ class AnnotationElement {
           const exportValue =
             typeof exportValues === "string" ? exportValues : null;
 
-          const domElement = document.getElementById(id);
+          const domElement = document.querySelector(
+            `[data-element-id="${id}"]`
+          );
           if (domElement && !GetElementsByNameSet.has(domElement)) {
             warn(`_getElementsByName - element not allowed: ${id}`);
             continue;
@@ -570,7 +572,7 @@ class LinkAnnotationElement extends AnnotationElement {
   render() {
     const { data, linkService } = this;
     const link = document.createElement("a");
-    link.setAttribute("id", data.id);
+    link.setAttribute("data-element-id", data.id);
     let isBound = false;
 
     if (data.url) {
@@ -775,8 +777,12 @@ class LinkAnnotationElement extends AnnotationElement {
           default:
             continue;
         }
-        const domElement = document.getElementById(id);
-        if (!domElement || !GetElementsByNameSet.has(domElement)) {
+
+        const domElement = document.querySelector(`[data-element-id="${id}"]`);
+        if (!domElement) {
+          continue;
+        } else if (!GetElementsByNameSet.has(domElement)) {
+          warn(`_bindResetFormAction - element not allowed: ${id}`);
           continue;
         }
         domElement.dispatchEvent(new Event("resetform"));
@@ -989,7 +995,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
       });
       const textContent = storedData.formattedValue || storedData.value || "";
       const elementData = {
-        userValue: null,
+        userValue: textContent,
         formattedValue: null,
         valueOnFocus: "",
       };
@@ -1009,13 +1015,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
         }
       }
       GetElementsByNameSet.add(element);
+      element.setAttribute("data-element-id", id);
+
       element.disabled = this.data.readOnly;
       element.name = this.data.fieldName;
       element.tabIndex = DEFAULT_TAB_INDEX;
 
-      elementData.userValue = textContent;
-      element.setAttribute("id", id);
-
       this._setRequired(element, this.data.required);
 
       element.addEventListener("input", event => {
@@ -1262,6 +1267,8 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
 
     const element = document.createElement("input");
     GetElementsByNameSet.add(element);
+    element.setAttribute("data-element-id", id);
+
     element.disabled = data.readOnly;
     this._setRequired(element, this.data.required);
     element.type = "checkbox";
@@ -1269,7 +1276,6 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
     if (value) {
       element.setAttribute("checked", true);
     }
-    element.setAttribute("id", id);
     element.setAttribute("exportValue", data.exportValue);
     element.tabIndex = DEFAULT_TAB_INDEX;
 
@@ -1346,6 +1352,8 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
 
     const element = document.createElement("input");
     GetElementsByNameSet.add(element);
+    element.setAttribute("data-element-id", id);
+
     element.disabled = data.readOnly;
     this._setRequired(element, this.data.required);
     element.type = "radio";
@@ -1353,7 +1361,6 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
     if (value) {
       element.setAttribute("checked", true);
     }
-    element.setAttribute("id", id);
     element.tabIndex = DEFAULT_TAB_INDEX;
 
     element.addEventListener("change", event => {
@@ -1463,10 +1470,11 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
 
     const selectElement = document.createElement("select");
     GetElementsByNameSet.add(selectElement);
+    selectElement.setAttribute("data-element-id", id);
+
     selectElement.disabled = this.data.readOnly;
     this._setRequired(selectElement, this.data.required);
     selectElement.name = this.data.fieldName;
-    selectElement.setAttribute("id", id);
     selectElement.tabIndex = DEFAULT_TAB_INDEX;
 
     let addAnEmptyEntry = this.data.combo && this.data.options.length > 0;
diff --git a/test/integration/annotation_spec.js b/test/integration/annotation_spec.js
index c25487cda..30b6c8806 100644
--- a/test/integration/annotation_spec.js
+++ b/test/integration/annotation_spec.js
@@ -13,7 +13,12 @@
  * limitations under the License.
  */
 
-const { closePages, loadAndWait } = require("./test_utils.js");
+const {
+  closePages,
+  getSelector,
+  getQuerySelector,
+  loadAndWait,
+} = require("./test_utils.js");
 
 describe("Annotation highlight", () => {
   describe("annotation-highlight.pdf", () => {
@@ -108,18 +113,14 @@ describe("Text widget", () => {
       const base = "hello world";
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          await page.type("#\\32 5R", base);
-          await page.waitForFunction(
-            `document.querySelector("#\\\\32 4R").value !== ""`
-          );
-          await page.waitForFunction(
-            `document.querySelector("#\\\\32 6R").value !== ""`
-          );
+          await page.type(getSelector("25R"), base);
+          await page.waitForFunction(`${getQuerySelector("24R")}.value !== ""`);
+          await page.waitForFunction(`${getQuerySelector("26R")}.value !== ""`);
 
-          let text = await page.$eval("#\\32 4R", el => el.value);
+          let text = await page.$eval(getSelector("24R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual(base);
 
-          text = await page.$eval("#\\32 6R", el => el.value);
+          text = await page.$eval(getSelector("26R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual(base);
         })
       );
@@ -145,15 +146,15 @@ describe("Annotation and storage", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           // Text field.
-          await page.type("#\\36 4R", text1);
+          await page.type(getSelector("64R"), text1);
           // Checkbox.
           await page.click("[data-annotation-id='65R']");
           // Radio.
           await page.click("[data-annotation-id='67R']");
 
           for (const [pageNumber, textId, checkId, radio1Id, radio2Id] of [
-            [2, "#\\31 8R", "#\\31 9R", "#\\32 1R", "#\\32 0R"],
-            [5, "#\\32 3R", "#\\32 4R", "#\\32 2R", "#\\32 5R"],
+            [2, "18R", "19R", "21R", "20R"],
+            [5, "23R", "24R", "22R", "25R"],
           ]) {
             await page.evaluate(n => {
               window.document
@@ -162,34 +163,37 @@ describe("Annotation and storage", () => {
             }, pageNumber);
 
             // Need to wait to have a displayed text input.
-            await page.waitForSelector(textId, {
+            await page.waitForSelector(getSelector(textId), {
               timeout: 0,
             });
 
-            const text = await page.$eval(textId, el => el.value);
+            const text = await page.$eval(getSelector(textId), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(text1);
 
-            let checked = await page.$eval(checkId, el => el.checked);
+            let checked = await page.$eval(
+              getSelector(checkId),
+              el => el.checked
+            );
             expect(checked).toEqual(true);
 
-            checked = await page.$eval(radio1Id, el => el.checked);
+            checked = await page.$eval(getSelector(radio1Id), el => el.checked);
             expect(checked).toEqual(false);
 
-            checked = await page.$eval(radio2Id, el => el.checked);
+            checked = await page.$eval(getSelector(radio2Id), el => el.checked);
             expect(checked).toEqual(false);
           }
 
           // Change data on page 5 and check that other pages changed.
           // Text field.
-          await page.type("#\\32 3R", text2);
+          await page.type(getSelector("23R"), text2);
           // Checkbox.
           await page.click("[data-annotation-id='24R']");
           // Radio.
           await page.click("[data-annotation-id='25R']");
 
           for (const [pageNumber, textId, checkId, radio1Id, radio2Id] of [
-            [1, "#\\36 4R", "#\\36 5R", "#\\36 7R", "#\\36 8R"],
-            [2, "#\\31 8R", "#\\31 9R", "#\\32 1R", "#\\32 0R"],
+            [1, "64R", "65R", "67R", "68R"],
+            [2, "18R", "19R", "21R", "20R"],
           ]) {
             await page.evaluate(n => {
               window.document
@@ -198,22 +202,25 @@ describe("Annotation and storage", () => {
             }, pageNumber);
 
             // Need to wait to have a displayed text input.
-            await page.waitForSelector(textId, {
+            await page.waitForSelector(getSelector(textId), {
               timeout: 0,
             });
 
-            const text = await page.$eval(textId, el => el.value);
+            const text = await page.$eval(getSelector(textId), el => el.value);
             expect(text)
               .withContext(`In ${browserName}`)
               .toEqual(text2 + text1);
 
-            let checked = await page.$eval(checkId, el => el.checked);
+            let checked = await page.$eval(
+              getSelector(checkId),
+              el => el.checked
+            );
             expect(checked).toEqual(false);
 
-            checked = await page.$eval(radio1Id, el => el.checked);
+            checked = await page.$eval(getSelector(radio1Id), el => el.checked);
             expect(checked).toEqual(false);
 
-            checked = await page.$eval(radio2Id, el => el.checked);
+            checked = await page.$eval(getSelector(radio2Id), el => el.checked);
             expect(checked).toEqual(false);
           }
         })
@@ -238,8 +245,8 @@ describe("ResetForm action", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           const base = "hello world";
-          for (let i = 3; i <= 7; i++) {
-            await page.type(`#\\36 ${i}R`, base);
+          for (let i = 63; i <= 67; i++) {
+            await page.type(getSelector(`${i}R`), base);
           }
 
           const selectors = [69, 71, 75].map(
@@ -249,36 +256,34 @@ describe("ResetForm action", () => {
             await page.click(selector);
           }
 
-          await page.select("#\\37 8R", "b");
-          await page.select("#\\38 1R", "f");
+          await page.select(getSelector("78R"), "b");
+          await page.select(getSelector("81R"), "f");
 
           await page.click("[data-annotation-id='82R']");
-          await page.waitForFunction(
-            `document.querySelector("#\\\\36 3R").value === ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("63R")}.value === ""`);
 
-          for (let i = 3; i <= 8; i++) {
-            const text = await page.$eval(`#\\36 ${i}R`, el => el.value);
+          for (let i = 63; i <= 68; i++) {
+            const text = await page.$eval(getSelector(`${i}R`), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual("");
           }
 
           const ids = [69, 71, 72, 73, 74, 75, 76, 77];
           for (const id of ids) {
             const checked = await page.$eval(
-              `#\\3${Math.floor(id / 10)} ${id % 10}R`,
+              getSelector(`${id}R`),
               el => el.checked
             );
             expect(checked).withContext(`In ${browserName}`).toEqual(false);
           }
 
           let selected = await page.$eval(
-            `#\\37 8R [value="a"]`,
+            `${getSelector("78R")} [value="a"]`,
             el => el.selected
           );
           expect(selected).withContext(`In ${browserName}`).toEqual(true);
 
           selected = await page.$eval(
-            `#\\38 1R [value="d"]`,
+            `${getSelector("81R")} [value="d"]`,
             el => el.selected
           );
           expect(selected).withContext(`In ${browserName}`).toEqual(true);
@@ -290,8 +295,8 @@ describe("ResetForm action", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           const base = "hello world";
-          for (let i = 3; i <= 8; i++) {
-            await page.type(`#\\36 ${i}R`, base);
+          for (let i = 63; i <= 68; i++) {
+            await page.type(getSelector(`${i}R`), base);
           }
 
           const selectors = [69, 71, 72, 73, 75].map(
@@ -301,24 +306,22 @@ describe("ResetForm action", () => {
             await page.click(selector);
           }
 
-          await page.select("#\\37 8R", "b");
-          await page.select("#\\38 1R", "f");
+          await page.select(getSelector("78R"), "b");
+          await page.select(getSelector("81R"), "f");
 
           await page.click("[data-annotation-id='84R']");
-          await page.waitForFunction(
-            `document.querySelector("#\\\\36 3R").value === ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("63R")}.value === ""`);
 
-          for (let i = 3; i <= 8; i++) {
+          for (let i = 63; i <= 68; i++) {
             const expected = (i - 3) % 2 === 0 ? "" : base;
-            const text = await page.$eval(`#\\36 ${i}R`, el => el.value);
+            const text = await page.$eval(getSelector(`${i}R`), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expected);
           }
 
           let ids = [69, 72, 73, 74, 76, 77];
           for (const id of ids) {
             const checked = await page.$eval(
-              `#\\3${Math.floor(id / 10)} ${id % 10}R`,
+              getSelector(`${id}R`),
               el => el.checked
             );
             expect(checked)
@@ -329,20 +332,20 @@ describe("ResetForm action", () => {
           ids = [71, 75];
           for (const id of ids) {
             const checked = await page.$eval(
-              `#\\3${Math.floor(id / 10)} ${id % 10}R`,
+              getSelector(`${id}R`),
               el => el.checked
             );
             expect(checked).withContext(`In ${browserName}`).toEqual(true);
           }
 
           let selected = await page.$eval(
-            `#\\37 8R [value="a"]`,
+            `${getSelector("78R")} [value="a"]`,
             el => el.selected
           );
           expect(selected).withContext(`In ${browserName}`).toEqual(true);
 
           selected = await page.$eval(
-            `#\\38 1R [value="f"]`,
+            `${getSelector("81R")} [value="f"]`,
             el => el.selected
           );
           expect(selected).withContext(`In ${browserName}`).toEqual(true);
diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js
index 2e7450050..0c1107e8d 100644
--- a/test/integration/scripting_spec.js
+++ b/test/integration/scripting_spec.js
@@ -13,7 +13,14 @@
  * limitations under the License.
  */
 
-const { clearInput, closePages, loadAndWait } = require("./test_utils.js");
+const {
+  clearInput,
+  closePages,
+  getSelector,
+  getQuerySelector,
+  getComputedStyleSelector,
+  loadAndWait,
+} = require("./test_utils.js");
 
 describe("Interaction", () => {
   async function actAndWaitForInput(page, selector, action, clear = true) {
@@ -22,7 +29,7 @@ describe("Interaction", () => {
     }
     await action();
     await page.waitForFunction(
-      `document.querySelector("${selector.replace("\\", "\\\\")}").value !== ""`
+      `document.querySelector('${selector}').value !== ""`
     );
     return page.$eval(selector, el => el.value);
   }
@@ -31,7 +38,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("160F-2019.pdf", "#\\34 16R");
+      pages = await loadAndWait("160F-2019.pdf", getSelector("416R"));
     });
 
     afterAll(async () => {
@@ -45,11 +52,11 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          // The document has an open action in order to give
-          // the focus to 401R.
-          const id = await page.evaluate(
-            () => window.document.activeElement.id
-          );
+          // The document has an open action in order to give the focus to 401R.
+          const id = await page.evaluate(() => {
+            const element = window.document.activeElement;
+            return element.getAttribute("data-element-id");
+          });
           expect(id).withContext(`In ${browserName}`).toEqual("401R");
         })
       );
@@ -59,20 +66,20 @@ describe("Interaction", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           let visibility = await page.$eval(
-            "#\\34 27R",
+            getSelector("427R"),
             el => getComputedStyle(el).visibility
           );
           expect(visibility).withContext(`In ${browserName}`).toEqual("hidden");
 
-          await page.type("#\\34 16R", "3.14159", { delay: 200 });
-          await page.click("#\\34 19R");
+          await page.type(getSelector("416R"), "3.14159", { delay: 200 });
+          await page.click(getSelector("419R"));
 
           await page.waitForFunction(
-            `getComputedStyle(document.querySelector("#\\\\34 27R")).visibility !== "hidden"`
+            `${getComputedStyleSelector("427R")}.visibility !== "hidden"`
           );
 
           visibility = await page.$eval(
-            "#\\34 27R",
+            getSelector("427R"),
             el => getComputedStyle(el).visibility
           );
           expect(visibility)
@@ -80,16 +87,16 @@ describe("Interaction", () => {
             .toEqual("visible");
 
           // Clear the textfield
-          await clearInput(page, "#\\34 16R");
+          await clearInput(page, getSelector("416R"));
           // and leave it
-          await page.click("#\\34 19R");
+          await page.click(getSelector("419R"));
 
           await page.waitForFunction(
-            `getComputedStyle(document.querySelector("#\\\\34 27R")).visibility !== "visible"`
+            `${getComputedStyleSelector("427R")}.visibility !== "visible"`
           );
 
           visibility = await page.$eval(
-            "#\\34 27R",
+            getSelector("427R"),
             el => getComputedStyle(el).visibility
           );
           expect(visibility).withContext(`In ${browserName}`).toEqual("hidden");
@@ -100,17 +107,16 @@ describe("Interaction", () => {
     it("must format the field with 2 digits and leave field with a click", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          await page.type("#\\34 16R", "3.14159", { delay: 200 });
-          await page.click("#\\34 19R");
+          await page.type(getSelector("416R"), "3.14159", { delay: 200 });
+          await page.click(getSelector("419R"));
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 16R").value !== "3.14159"`
-          );
+          const valueFnStr = `${getQuerySelector("416R")}.value !== "3.14159"`;
+          await page.waitForFunction(valueFnStr);
 
-          const text = await page.$eval("#\\34 16R", el => el.value);
+          const text = await page.$eval(getSelector("416R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("3,14");
 
-          const sum = await page.$eval("#\\34 27R", el => el.value);
+          const sum = await page.$eval(getSelector("427R"), el => el.value);
           expect(sum).withContext(`In ${browserName}`).toEqual("3,14");
         })
       );
@@ -119,36 +125,37 @@ describe("Interaction", () => {
     it("must format the field with 2 digits, leave field with a click and again", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          await page.type("#\\34 48R", "61803", { delay: 200 });
-          await page.click("#\\34 19R");
+          await page.type(getSelector("448R"), "61803", { delay: 200 });
+          await page.click(getSelector("419R"));
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 48R").value !== "61803"`
-          );
+          const valueOneFnStr = `${getQuerySelector("448R")}.value !== "61803"`;
+          await page.waitForFunction(valueOneFnStr);
 
-          let text = await page.$eval("#\\34 48R", el => el.value);
+          let text = await page.$eval(getSelector("448R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("61.803,00");
 
-          await page.click("#\\34 48R");
+          await page.click(getSelector("448R"));
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 48R").value !== "61.803,00"`
-          );
+          const valueTwoFnStr = `${getQuerySelector(
+            "448R"
+          )}.value !== "61.803,00"`;
+          await page.waitForFunction(valueTwoFnStr);
 
-          text = await page.$eval("#\\34 48R", el => el.value);
+          text = await page.$eval(getSelector("448R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("61803");
 
           // Clear the textfield
-          await clearInput(page, "#\\34 48R");
+          await clearInput(page, getSelector("448R"));
 
-          await page.type("#\\34 48R", "1.61803", { delay: 200 });
-          await page.click("#\\34 19R");
+          await page.type(getSelector("448R"), "1.61803", { delay: 200 });
+          await page.click(getSelector("419R"));
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 48R").value !== "1.61803"`
-          );
+          const valueThreeFnStr = `${getQuerySelector(
+            "448R"
+          )}.value !== "1.61803"`;
+          await page.waitForFunction(valueThreeFnStr);
 
-          text = await page.$eval("#\\34 48R", el => el.value);
+          text = await page.$eval(getSelector("448R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("1,62");
         })
       );
@@ -157,23 +164,23 @@ describe("Interaction", () => {
     it("must format the field with 2 digits and leave field with a TAB", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          const prevSum = await page.$eval("#\\34 27R", el => el.value);
+          const prevSum = await page.$eval(getSelector("427R"), el => el.value);
 
-          await page.type("#\\34 22R", "2.7182818", { delay: 200 });
+          await page.type(getSelector("422R"), "2.7182818", { delay: 200 });
           await page.keyboard.press("Tab");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\34 22R").value !== "2.7182818"`
+            `${getQuerySelector("422R")}.value !== "2.7182818"`
           );
 
-          const text = await page.$eval("#\\34 22R", el => el.value);
+          const text = await page.$eval(getSelector("422R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("2,72");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\34 27R").value !== "${prevSum}"`
+            `${getQuerySelector("427R")}.value !== "${prevSum}"`
           );
 
-          const sum = await page.$eval("#\\34 27R", el => el.value);
+          const sum = await page.$eval(getSelector("427R"), el => el.value);
           expect(sum).withContext(`In ${browserName}`).toEqual("5,86");
         })
       );
@@ -182,20 +189,20 @@ describe("Interaction", () => {
     it("must format the field with 2 digits and hit ESC", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          let sum = await page.$eval("#\\34 71R", el => el.value);
+          let sum = await page.$eval(getSelector("471R"), el => el.value);
           expect(sum).withContext(`In ${browserName}`).toEqual("4,24");
 
-          await page.type("#\\34 36R", "0.69314", { delay: 200 });
+          await page.type(getSelector("436R"), "0.69314", { delay: 200 });
           await page.keyboard.press("Escape");
 
-          const text = await page.$eval("#\\34 36R", el => el.value);
+          const text = await page.$eval(getSelector("436R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("0.69314");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\34 71R").value !== "${sum}"`
+            `${getQuerySelector("471R")}.value !== "${sum}"`
           );
 
-          sum = await page.$eval("#\\34 71R", el => el.value);
+          sum = await page.$eval(getSelector("471R"), el => el.value);
           expect(sum).withContext(`In ${browserName}`).toEqual("3,55");
         })
       );
@@ -204,18 +211,18 @@ describe("Interaction", () => {
     it("must format the field with 2 digits on key ENTER", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          const prevSum = await page.$eval("#\\34 27R", el => el.value);
+          const prevSum = await page.$eval(getSelector("427R"), el => el.value);
 
-          await page.type("#\\34 19R", "0.577215", { delay: 200 });
+          await page.type(getSelector("419R"), "0.577215", { delay: 200 });
           await page.keyboard.press("Enter");
-          const text = await page.$eval("#\\34 19R", el => el.value);
+          const text = await page.$eval(getSelector("419R"), el => el.value);
           expect(text).toEqual("0.577215");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\34 27R").value !== "${prevSum}"`
+            `${getQuerySelector("427R")}.value !== "${prevSum}"`
           );
 
-          const sum = await page.$eval("#\\34 27R", el => el.value);
+          const sum = await page.$eval(getSelector("427R"), el => el.value);
           expect(sum).toEqual("6,44");
         })
       );
@@ -228,39 +235,38 @@ describe("Interaction", () => {
           await page.click("[data-annotation-id='449R']");
 
           // this field has no actions but it must be cleared on reset
-          await page.type("#\\34 05R", "employee", { delay: 200 });
+          await page.type(getSelector("405R"), "employee", { delay: 200 });
 
-          let checked = await page.$eval("#\\34 49R", el => el.checked);
+          let checked = await page.$eval(getSelector("449R"), el => el.checked);
           expect(checked).toEqual(true);
 
           // click on reset button
           await page.click("[data-annotation-id='402R']");
 
           await Promise.all(
-            ["16", "22", "19", "05"].map(id =>
-              page.waitForFunction(
-                `document.querySelector("#\\\\34 ${id}R").value === ""`
-              )
-            )
+            ["416R", "422R", "419R", "405R"].map(id => {
+              const querySelector = getQuerySelector(id);
+              return page.waitForFunction(`${querySelector}.value === ""`);
+            })
           );
 
-          let text = await page.$eval("#\\34 16R", el => el.value);
+          let text = await page.$eval(getSelector("416R"), el => el.value);
           expect(text).toEqual("");
 
-          text = await page.$eval("#\\34 22R", el => el.value);
+          text = await page.$eval(getSelector("422R"), el => el.value);
           expect(text).toEqual("");
 
-          text = await page.$eval("#\\34 19R", el => el.value);
+          text = await page.$eval(getSelector("419R"), el => el.value);
           expect(text).toEqual("");
 
-          text = await page.$eval("#\\34 05R", el => el.value);
+          text = await page.$eval(getSelector("405R"), el => el.value);
           expect(text).toEqual("");
 
-          checked = await page.$eval("#\\34 49R", el => el.checked);
+          checked = await page.$eval(getSelector("449R"), el => el.checked);
           expect(checked).toEqual(false);
 
           const visibility = await page.$eval(
-            "#\\34 27R",
+            getSelector("427R"),
             el => getComputedStyle(el).visibility
           );
           expect(visibility).toEqual("hidden");
@@ -273,7 +279,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("js-buttons.pdf", "#\\38 0R");
+      pages = await loadAndWait("js-buttons.pdf", getSelector("80R"));
     });
 
     afterAll(async () => {
@@ -288,20 +294,20 @@ describe("Interaction", () => {
           );
 
           const expected = [
-            ["#\\38 1R", "Group1=Choice1::1"],
-            ["#\\38 2R", "Group1=Choice2::2"],
-            ["#\\38 3R", "Group1=Choice3::3"],
-            ["#\\38 4R", "Group1=Choice4::4"],
+            ["81R", "Group1=Choice1::1"],
+            ["82R", "Group1=Choice2::2"],
+            ["83R", "Group1=Choice3::3"],
+            ["84R", "Group1=Choice4::4"],
           ];
-          for (const [selector, expectedText] of expected) {
+          for (const [id, expectedText] of expected) {
             // Clear the textfield
-            await clearInput(page, "#\\38 0R");
+            await clearInput(page, getSelector("80R"));
 
-            await page.click(selector);
+            await page.click(getSelector(id));
             await page.waitForFunction(
-              `document.querySelector("#\\\\38 0R").value !== ""`
+              `${getQuerySelector("80R")}.value !== ""`
             );
-            const text = await page.$eval("#\\38 0R", el => el.value);
+            const text = await page.$eval(getSelector("80R"), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expectedText);
           }
         })
@@ -312,24 +318,24 @@ describe("Interaction", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           const expected = [
-            ["#\\38 5R", "Check1=Yes::5"],
-            ["#\\38 7R", "Check2=Yes::6"],
-            ["#\\38 8R", "Check3=Yes::7"],
-            ["#\\38 9R", "Check4=Yes::8"],
-            ["#\\38 5R", "Check1=Off::5"],
-            ["#\\38 7R", "Check2=Off::6"],
-            ["#\\38 8R", "Check3=Off::7"],
-            ["#\\38 9R", "Check4=Off::8"],
+            ["85R", "Check1=Yes::5"],
+            ["87R", "Check2=Yes::6"],
+            ["88R", "Check3=Yes::7"],
+            ["89R", "Check4=Yes::8"],
+            ["85R", "Check1=Off::5"],
+            ["87R", "Check2=Off::6"],
+            ["88R", "Check3=Off::7"],
+            ["89R", "Check4=Off::8"],
           ];
-          for (const [selector, expectedText] of expected) {
+          for (const [id, expectedText] of expected) {
             // Clear the textfield
-            await clearInput(page, "#\\38 0R");
+            await clearInput(page, getSelector("80R"));
 
-            await page.click(selector);
+            await page.click(getSelector(id));
             await page.waitForFunction(
-              `document.querySelector("#\\\\38 0R").value !== ""`
+              `${getQuerySelector("80R")}.value !== ""`
             );
-            const text = await page.$eval("#\\38 0R", el => el.value);
+            const text = await page.$eval(getSelector("80R"), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expectedText);
           }
         })
@@ -340,21 +346,21 @@ describe("Interaction", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           const expected = [
-            ["#\\39 0R", "Check5=Yes1::9"],
-            ["#\\39 1R", "Check5=Yes2::10"],
-            ["#\\39 2R", "Check5=Yes3::11"],
-            ["#\\39 3R", "Check5=Yes4::12"],
-            ["#\\39 3R", "Check5=Off::12"],
+            ["90R", "Check5=Yes1::9"],
+            ["91R", "Check5=Yes2::10"],
+            ["92R", "Check5=Yes3::11"],
+            ["93R", "Check5=Yes4::12"],
+            ["93R", "Check5=Off::12"],
           ];
-          for (const [selector, expectedText] of expected) {
+          for (const [id, expectedText] of expected) {
             // Clear the textfield
-            await clearInput(page, "#\\38 0R");
+            await clearInput(page, getSelector("80R"));
 
-            await page.click(selector);
+            await page.click(getSelector(id));
             await page.waitForFunction(
-              `document.querySelector("#\\\\38 0R").value !== ""`
+              `${getQuerySelector("80R")}.value !== ""`
             );
-            const text = await page.$eval("#\\38 0R", el => el.value);
+            const text = await page.$eval(getSelector("80R"), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expectedText);
           }
         })
@@ -366,25 +372,25 @@ describe("Interaction", () => {
         pages.map(async ([browserName, page]) => {
           const expected = [
             ["", "Off;Off"],
-            ["#\\39 4R", "Yes;Off"],
-            ["#\\39 5R", "Yes;NoAct2"],
-            ["#\\39 6R", "Yes;NoAct3"],
-            ["#\\39 4R", "Off;NoAct3"],
-            ["#\\39 5R", "Off;NoAct2"],
+            ["94R", "Yes;Off"],
+            ["95R", "Yes;NoAct2"],
+            ["96R", "Yes;NoAct3"],
+            ["94R", "Off;NoAct3"],
+            ["95R", "Off;NoAct2"],
           ];
-          for (const [selector, expectedText] of expected) {
+          for (const [id, expectedText] of expected) {
             // Clear the textfield
-            await clearInput(page, "#\\38 0R");
+            await clearInput(page, getSelector("80R"));
 
-            if (selector) {
-              await page.click(selector);
+            if (id) {
+              await page.click(getSelector(id));
             }
 
             await page.click("[data-annotation-id='97R']");
             await page.waitForFunction(
-              `document.querySelector("#\\\\38 0R").value !== ""`
+              `${getQuerySelector("80R")}.value !== ""`
             );
-            const text = await page.$eval("#\\38 0R", el => el.value);
+            const text = await page.$eval(getSelector("80R"), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expectedText);
           }
         })
@@ -396,7 +402,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("doc_actions.pdf", "#\\34 7R");
+      pages = await loadAndWait("doc_actions.pdf", getSelector("47R"));
     });
 
     afterAll(async () => {
@@ -413,24 +419,24 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await clearInput(page, "#\\34 7R");
+          await clearInput(page, getSelector("47R"));
           await page.evaluate(_ => {
             window.document.activeElement.blur();
           });
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 7R").value === ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("47R")}.value === ""`);
 
-          let text = await actAndWaitForInput(page, "#\\34 7R", async () => {
-            await page.click("#print");
-          });
+          let text = await actAndWaitForInput(
+            page,
+            getSelector("47R"),
+            async () => {
+              await page.click("#print");
+            }
+          );
           expect(text).withContext(`In ${browserName}`).toEqual("WillPrint");
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\35 0R").value !== ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("50R")}.value !== ""`);
 
-          text = await page.$eval("#\\35 0R", el => el.value);
+          text = await page.$eval(getSelector("50R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("DidPrint");
         })
       );
@@ -441,7 +447,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("doc_actions.pdf", "#\\34 7R");
+      pages = await loadAndWait("doc_actions.pdf", getSelector("47R"));
     });
 
     afterAll(async () => {
@@ -462,24 +468,24 @@ describe("Interaction", () => {
               behavior: "deny",
             });
           } catch (_) {}
-          await clearInput(page, "#\\34 7R");
+          await clearInput(page, getSelector("47R"));
           await page.evaluate(_ => {
             window.document.activeElement.blur();
           });
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 7R").value === ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("47R")}.value === ""`);
 
-          let text = await actAndWaitForInput(page, "#\\34 7R", async () => {
-            await page.click("#download");
-          });
+          let text = await actAndWaitForInput(
+            page,
+            getSelector("47R"),
+            async () => {
+              await page.click("#download");
+            }
+          );
           expect(text).withContext(`In ${browserName}`).toEqual("WillSave");
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\35 0R").value !== ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("50R")}.value !== ""`);
 
-          text = await page.$eval("#\\35 0R", el => el.value);
+          text = await page.$eval(getSelector("50R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("DidSave");
         })
       );
@@ -490,7 +496,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("doc_actions.pdf", "#\\34 7R");
+      pages = await loadAndWait("doc_actions.pdf", getSelector("47R"));
     });
 
     afterAll(async () => {
@@ -504,37 +510,34 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.waitForFunction(
-            `document.querySelector("#\\\\34 7R").value !== ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("47R")}.value !== ""`);
 
-          let text = await page.$eval("#\\34 7R", el => el.value);
+          let text = await page.$eval(getSelector("47R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("PageOpen 1");
 
           for (let run = 0; run < 5; run++) {
-            for (const ref of [18, 19, 20, 21, 47, 50]) {
-              await page.evaluate(refElem => {
-                const element = window.document.getElementById(`${refElem}R`);
+            for (const ref of ["18R", "19R", "20R", "21R", "47R", "50R"]) {
+              await page.evaluate(selector => {
+                const element = window.document.querySelector(selector);
                 if (element) {
                   element.value = "";
                 }
-              }, ref);
+              }, getSelector(ref));
             }
 
             for (const [refOpen, refClose, pageNumOpen, pageNumClose] of [
-              [18, 50, 2, 1],
-              [21, 19, 3, 2],
-              [47, 20, 1, 3],
+              ["18R", "50R", 2, 1],
+              ["21R", "19R", 3, 2],
+              ["47R", "20R", 1, 3],
             ]) {
               text = await actAndWaitForInput(
                 page,
-                `#\\3${Math.floor(refOpen / 10)} ${refOpen % 10}R`,
+                getSelector(refOpen),
                 async () => {
-                  await page.evaluate(refElem => {
-                    window.document
-                      .getElementById(`${refElem}R`)
-                      .scrollIntoView();
-                  }, refOpen);
+                  await page.evaluate(selector => {
+                    const element = window.document.querySelector(selector);
+                    element.scrollIntoView();
+                  }, getSelector(refOpen));
                 },
                 false
               );
@@ -542,10 +545,7 @@ describe("Interaction", () => {
                 .withContext(`In ${browserName}`)
                 .toEqual(`PageOpen ${pageNumOpen}`);
 
-              text = await page.$eval(
-                `#\\3${Math.floor(refClose / 10)} ${refClose % 10}R`,
-                el => el.value
-              );
+              text = await page.$eval(getSelector(refClose), el => el.value);
               expect(text)
                 .withContext(`In ${browserName}`)
                 .toEqual(`PageClose ${pageNumClose}`);
@@ -560,7 +560,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("js-authors.pdf", "#\\32 5R");
+      pages = await loadAndWait("js-authors.pdf", getSelector("25R"));
     });
 
     afterAll(async () => {
@@ -570,9 +570,13 @@ describe("Interaction", () => {
     it("must print authors in a text field", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          const text = await actAndWaitForInput(page, "#\\32 5R", async () => {
-            await page.click("[data-annotation-id='26R']");
-          });
+          const text = await actAndWaitForInput(
+            page,
+            getSelector("25R"),
+            async () => {
+              await page.click("[data-annotation-id='26R']");
+            }
+          );
           expect(text)
             .withContext(`In ${browserName}`)
             .toEqual("author1::author2::author3::author4::author5");
@@ -585,7 +589,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("listbox_actions.pdf", "#\\33 3R");
+      pages = await loadAndWait("listbox_actions.pdf", getSelector("33R"));
     });
 
     afterAll(async () => {
@@ -596,12 +600,12 @@ describe("Interaction", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           for (const num of [7, 6, 4, 3, 2, 1]) {
-            await clearInput(page, "#\\33 3R");
+            await clearInput(page, getSelector("33R"));
             await page.click(`option[value=Export${num}]`);
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 3R").value !== ""`
+              `${getQuerySelector("33R")}.value !== ""`
             );
-            const text = await page.$eval("#\\33 3R", el => el.value);
+            const text = await page.$eval(getSelector("33R"), el => el.value);
             expect(text)
               .withContext(`In ${browserName}`)
               .toEqual(`Item${num},Export${num}`);
@@ -616,22 +620,22 @@ describe("Interaction", () => {
           // Click on ClearItems button.
           await page.click("[data-annotation-id='34R']");
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").children.length === 0`
+            `${getQuerySelector("30R")}.children.length === 0`
           );
 
           // Click on Restore button.
           await page.click("[data-annotation-id='37R']");
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").children.length !== 0`
+            `${getQuerySelector("30R")}.children.length !== 0`
           );
 
           for (const num of [7, 6, 4, 3, 2, 1]) {
-            await clearInput(page, "#\\33 3R");
+            await clearInput(page, getSelector("33R"));
             await page.click(`option[value=Export${num}]`);
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 3R").value !== ""`
+              `${getQuerySelector("33R")}.value !== ""`
             );
-            const text = await page.$eval("#\\33 3R", el => el.value);
+            const text = await page.$eval(getSelector("33R"), el => el.value);
             expect(text)
               .withContext(`In ${browserName}`)
               .toEqual(`Item${num},Export${num}`);
@@ -646,26 +650,30 @@ describe("Interaction", () => {
           let len = 6;
           for (const num of [1, 3, 5, 6, 431, -1, 0]) {
             ++len;
-            await clearInput(page, "#\\33 3R");
-            await clearInput(page, "#\\33 9R");
-            await page.type("#\\33 9R", `${num},Insert${num},Tresni${num}`, {
-              delay: 10,
-            });
+            await clearInput(page, getSelector("33R"));
+            await clearInput(page, getSelector("39R"));
+            await page.type(
+              getSelector("39R"),
+              `${num},Insert${num},Tresni${num}`,
+              {
+                delay: 10,
+              }
+            );
 
             // Click on AddItem button.
             await page.click("[data-annotation-id='38R']");
 
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 0R").children.length === ${len}`
+              `${getQuerySelector("30R")}.children.length === ${len}`
             );
 
             // Click on newly added option.
-            await page.select("#\\33 0R", `Tresni${num}`);
+            await page.select(getSelector("30R"), `Tresni${num}`);
 
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 3R").value !== ""`
+              `${getQuerySelector("33R")}.value !== ""`
             );
-            const text = await page.$eval("#\\33 3R", el => el.value);
+            const text = await page.$eval(getSelector("33R"), el => el.value);
             expect(text)
               .withContext(`In ${browserName}`)
               .toEqual(`Insert${num},Tresni${num}`);
@@ -679,32 +687,32 @@ describe("Interaction", () => {
         pages.map(async ([browserName, page]) => {
           let len = 6;
           // Click on Restore button.
-          await clearInput(page, "#\\33 3R");
+          await clearInput(page, getSelector("33R"));
           await page.click("[data-annotation-id='37R']");
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").children.length === ${len}`
+            `${getQuerySelector("30R")}.children.length === ${len}`
           );
 
           for (const num of [2, 5]) {
             --len;
-            await clearInput(page, "#\\33 9R");
-            await page.type("#\\33 9R", `${num}`);
+            await clearInput(page, getSelector("39R"));
+            await page.type(getSelector("39R"), `${num}`);
 
             // Click on DeleteItem button.
             await page.click("[data-annotation-id='36R']");
 
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 0R").children.length === ${len}`
+              `${getQuerySelector("30R")}.children.length === ${len}`
             );
           }
 
           for (const num of [6, 4, 2, 1]) {
-            await clearInput(page, "#\\33 3R");
+            await clearInput(page, getSelector("33R"));
             await page.click(`option[value=Export${num}]`);
             await page.waitForFunction(
-              `document.querySelector("#\\\\33 3R").value !== ""`
+              `${getQuerySelector("33R")}.value !== ""`
             );
-            const text = await page.$eval("#\\33 3R", el => el.value);
+            const text = await page.$eval(getSelector("33R"), el => el.value);
             expect(text)
               .withContext(`In ${browserName}`)
               .toEqual(`Item${num},Export${num}`);
@@ -718,7 +726,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("js-colors.pdf", "#\\33 4R");
+      pages = await loadAndWait("js-colors.pdf", getSelector("34R"));
     });
 
     afterAll(async () => {
@@ -729,13 +737,13 @@ describe("Interaction", () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           for (const [name, ref] of [
-            ["Text1", "#\\33 4R"],
-            ["Check1", "#\\33 5R"],
-            ["Radio1", "#\\33 7R"],
-            ["Choice1", "#\\33 8R"],
+            ["Text1", "34R"],
+            ["Check1", "35R"],
+            ["Radio1", "37R"],
+            ["Choice1", "38R"],
           ]) {
-            await clearInput(page, "#\\33 4R");
-            await page.type("#\\33 4R", `${name}`, {
+            await clearInput(page, getSelector("34R"));
+            await page.type(getSelector("34R"), `${name}`, {
               delay: 10,
             });
 
@@ -745,19 +753,20 @@ describe("Interaction", () => {
               [44, "border-top-color", "rgb(0, 0, 255)"],
             ]) {
               const current = await page.$eval(
-                ref,
+                getSelector(ref),
                 (el, _propName) => getComputedStyle(el)[_propName],
                 propName
               );
 
               await page.click(`[data-annotation-id='${id}R']`);
-              const selector = ref.replace("\\", "\\\\");
               await page.waitForFunction(
-                `getComputedStyle(document.querySelector("${selector}"))["${propName}"] !== "${current}"`
+                `${getComputedStyleSelector(
+                  ref
+                )}["${propName}"] !== "${current}"`
               );
 
               const color = await page.$eval(
-                ref,
+                getSelector(ref),
                 (el, _propName) => getComputedStyle(el)[_propName],
                 propName
               );
@@ -773,7 +782,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue13132.pdf", "#\\31 71R");
+      pages = await loadAndWait("issue13132.pdf", getSelector("171R"));
     });
 
     afterAll(async () => {
@@ -787,30 +796,32 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.evaluate(() => {
-            window.document.getElementById("171R").scrollIntoView();
-          });
+          await page.evaluate(selector => {
+            window.document.querySelector(selector).scrollIntoView();
+          }, getSelector("171R"));
 
           let sum = 0;
           for (const [id, val] of [
-            ["#\\31 38R", 1],
-            ["#\\37 7R", 2],
-            ["#\\39 3R", 3],
-            ["#\\31 51R", 4],
-            ["#\\37 9R", 5],
+            ["138R", 1],
+            ["77R", 2],
+            ["93R", 3],
+            ["151R", 4],
+            ["79R", 5],
           ]) {
-            const prev = await page.$eval("#\\31 71R", el => el.value);
+            const prev = await page.$eval(getSelector("171R"), el => el.value);
 
-            await page.type(id, val.toString(), { delay: 100 });
+            await page.type(getSelector(id), val.toString(), {
+              delay: 100,
+            });
             await page.keyboard.press("Tab");
 
             await page.waitForFunction(
-              `document.querySelector("#\\\\31 71R").value !== "${prev}"`
+              `${getQuerySelector("171R")}.value !== "${prev}"`
             );
 
             sum += val;
 
-            const total = await page.$eval("#\\31 71R", el => el.value);
+            const total = await page.$eval(getSelector("171R"), el => el.value);
             expect(total).withContext(`In ${browserName}`).toEqual(`£${sum}`);
           }
 
@@ -821,11 +832,11 @@ describe("Interaction", () => {
               .querySelectorAll('[data-page-number="4"][class="page"]')[0]
               .scrollIntoView();
           });
-          await page.waitForSelector("#\\32 99R", {
+          await page.waitForSelector(getSelector("299R"), {
             timeout: 0,
           });
 
-          const total = await page.$eval("#\\32 99R", el => el.value);
+          const total = await page.$eval(getSelector("299R"), el => el.value);
           expect(total).withContext(`In ${browserName}`).toEqual(`£${sum}`);
         })
       );
@@ -836,7 +847,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("evaljs.pdf", "#\\35 5R");
+      pages = await loadAndWait("evaljs.pdf", getSelector("55R"));
     });
 
     afterAll(async () => {
@@ -850,9 +861,9 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await clearInput(page, "#\\35 5R");
+          await clearInput(page, getSelector("55R"));
           await page.type(
-            "#\\35 5R",
+            getSelector("55R"),
             `
             ['Text1', 'Text2', 'Text4',
              'List Box7', 'Group6'].map(x => this.getField(x).page).join(',')
@@ -861,11 +872,9 @@ describe("Interaction", () => {
 
           // Click on execute button to eval the above code.
           await page.click("[data-annotation-id='57R']");
-          await page.waitForFunction(
-            `document.querySelector("#\\\\35 6R").value !== ""`
-          );
+          await page.waitForFunction(`${getQuerySelector("56R")}.value !== ""`);
 
-          const text = await page.$eval("#\\35 6R", el => el.value);
+          const text = await page.$eval(getSelector("56R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("0,0,1,1,1");
         })
       );
@@ -881,23 +890,25 @@ describe("Interaction", () => {
             ["visible", "visible"],
           ]) {
             let visibility = await page.$eval(
-              "#\\35 6R",
+              getSelector("56R"),
               el => getComputedStyle(el).visibility
             );
 
-            await clearInput(page, "#\\35 5R");
+            await clearInput(page, getSelector("55R"));
             await page.type(
-              "#\\35 5R",
+              getSelector("55R"),
               `this.getField("Text2").display = display.${type};`
             );
 
             await page.click("[data-annotation-id='57R']");
             await page.waitForFunction(
-              `getComputedStyle(document.querySelector("#\\\\35 6R")).visibility !== "${visibility}"`
+              `${getComputedStyleSelector(
+                "56R"
+              )}.visibility !== "${visibility}"`
             );
 
             visibility = await page.$eval(
-              "#\\35 6R",
+              getSelector("56R"),
               el => getComputedStyle(el).visibility
             );
             expect(visibility).withContext(`In ${browserName}`).toEqual(vis);
@@ -911,7 +922,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue13269.pdf", "#\\32 7R");
+      pages = await loadAndWait("issue13269.pdf", getSelector("27R"));
     });
 
     afterAll(async () => {
@@ -925,20 +936,18 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.type("#\\32 7R", "hello");
+          await page.type(getSelector("27R"), "hello");
           await page.keyboard.press("Enter");
 
           await Promise.all(
-            [4, 5, 6].map(async n =>
-              page.waitForFunction(
-                `document.querySelector("#\\\\32 ${n}R").value !== ""`
-              )
+            ["24R", "25R", "26R"].map(async id =>
+              page.waitForFunction(`${getQuerySelector(id)}.value !== ""`)
             )
           );
 
           const expected = "hello world";
-          for (const n of [4, 5, 6]) {
-            const text = await page.$eval(`#\\32 ${n}R`, el => el.value);
+          for (const id of ["24R", "25R", "26R"]) {
+            const text = await page.$eval(getSelector(id), el => el.value);
             expect(text).withContext(`In ${browserName}`).toEqual(expected);
           }
         })
@@ -950,7 +959,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("secHandler.pdf", "#\\32 5R");
+      pages = await loadAndWait("secHandler.pdf", getSelector("25R"));
     });
 
     afterAll(async () => {
@@ -959,9 +968,13 @@ describe("Interaction", () => {
     it("must print securityHandler value in a text field", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
-          const text = await actAndWaitForInput(page, "#\\32 5R", async () => {
-            await page.click("[data-annotation-id='26R']");
-          });
+          const text = await actAndWaitForInput(
+            page,
+            getSelector("25R"),
+            async () => {
+              await page.click("[data-annotation-id='26R']");
+            }
+          );
           expect(text).withContext(`In ${browserName}`).toEqual("Standard");
         })
       );
@@ -972,7 +985,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue14307.pdf", "#\\33 0R");
+      pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
       pages.map(async ([, page]) => {
         page.on("dialog", async dialog => {
           await dialog.dismiss();
@@ -991,34 +1004,34 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await clearInput(page, "#\\32 9R");
-          await clearInput(page, "#\\33 0R");
+          await clearInput(page, getSelector("29R"));
+          await clearInput(page, getSelector("30R"));
 
-          await page.focus("#\\32 9R");
-          await page.type("#\\32 9R", "12A", { delay: 100 });
+          await page.focus(getSelector("29R"));
+          await page.type(getSelector("29R"), "12A", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 9R").value !== "12A"`
+            `${getQuerySelector("29R")}.value !== "12A"`
           );
 
-          let text = await page.$eval(`#\\32 9R`, el => el.value);
+          let text = await page.$eval(getSelector(`29R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("12");
 
-          await page.focus("#\\32 9R");
-          await page.type("#\\32 9R", "34", { delay: 100 });
+          await page.focus(getSelector("29R"));
+          await page.type(getSelector("29R"), "34", { delay: 100 });
           await page.click("[data-annotation-id='30R']");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 9R").value !== "1234"`
+            `${getQuerySelector("29R")}.value !== "1234"`
           );
 
-          text = await page.$eval(`#\\32 9R`, el => el.value);
+          text = await page.$eval(getSelector(`29R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("");
 
-          await page.focus("#\\32 9R");
-          await page.type("#\\32 9R", "12345", { delay: 100 });
+          await page.focus(getSelector("29R"));
+          await page.type(getSelector("29R"), "12345", { delay: 100 });
           await page.click("[data-annotation-id='30R']");
 
-          text = await page.$eval(`#\\32 9R`, el => el.value);
+          text = await page.$eval(getSelector(`29R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("12345");
         })
       );
@@ -1029,7 +1042,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue14307.pdf", "#\\33 0R");
+      pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
       pages.map(async ([, page]) => {
         page.on("dialog", async dialog => {
           await dialog.dismiss();
@@ -1048,34 +1061,34 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await clearInput(page, "#\\32 9R");
-          await clearInput(page, "#\\33 0R");
+          await clearInput(page, getSelector("29R"));
+          await clearInput(page, getSelector("30R"));
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "(123) 456A", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "(123) 456A", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").value !== "(123) 456A"`
+            `${getQuerySelector("30R")}.value !== "(123) 456A"`
           );
 
-          let text = await page.$eval(`#\\33 0R`, el => el.value);
+          let text = await page.$eval(getSelector(`30R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("(123) 456");
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "-789", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "-789", { delay: 100 });
           await page.click("[data-annotation-id='29R']");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").value !== "(123) 456-789"`
+            `${getQuerySelector("30R")}.value !== "(123) 456-789"`
           );
 
-          text = await page.$eval(`#\\33 0R`, el => el.value);
+          text = await page.$eval(getSelector(`30R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("");
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "(123) 456-7890", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "(123) 456-7890", { delay: 100 });
           await page.click("[data-annotation-id='29R']");
 
-          text = await page.$eval(`#\\33 0R`, el => el.value);
+          text = await page.$eval(getSelector("30R"), el => el.value);
           expect(text)
             .withContext(`In ${browserName}`)
             .toEqual("(123) 456-7890");
@@ -1088,7 +1101,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue14307.pdf", "#\\33 0R");
+      pages = await loadAndWait("issue14307.pdf", getSelector("30R"));
       pages.map(async ([, page]) => {
         page.on("dialog", async dialog => {
           await dialog.dismiss();
@@ -1107,34 +1120,34 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await clearInput(page, "#\\32 9R");
-          await clearInput(page, "#\\33 0R");
+          await clearInput(page, getSelector("29R"));
+          await clearInput(page, getSelector("30R"));
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "123A", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "123A", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").value !== "123A"`
+            `${getQuerySelector("30R")}.value !== "123A"`
           );
 
-          let text = await page.$eval(`#\\33 0R`, el => el.value);
+          let text = await page.$eval(getSelector(`30R`), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("123");
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "-456", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "-456", { delay: 100 });
           await page.click("[data-annotation-id='29R']");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\33 0R").value !== "123-456"`
+            `${getQuerySelector("30R")}.value !== "123-456"`
           );
 
-          text = await page.$eval(`#\\33 0R`, el => el.value);
+          text = await page.$eval(getSelector("30R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("");
 
-          await page.focus("#\\33 0R");
-          await page.type("#\\33 0R", "123-4567", { delay: 100 });
+          await page.focus(getSelector("30R"));
+          await page.type(getSelector("30R"), "123-4567", { delay: 100 });
           await page.click("[data-annotation-id='29R']");
 
-          text = await page.$eval(`#\\33 0R`, el => el.value);
+          text = await page.$eval(getSelector("30R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("123-4567");
         })
       );
@@ -1145,7 +1158,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue14862.pdf", "#\\32 7R");
+      pages = await loadAndWait("issue14862.pdf", getSelector("27R"));
       pages.map(async ([, page]) => {
         page.on("dialog", async dialog => {
           await dialog.dismiss();
@@ -1157,69 +1170,69 @@ describe("Interaction", () => {
       await closePages(pages);
     });
 
-    it("must convert input in uppercase", async () => {
+    it("must convert input to uppercase", async () => {
       await Promise.all(
         pages.map(async ([browserName, page]) => {
           await page.waitForFunction(
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.type("#\\32 7R", "Hello", { delay: 100 });
+          await page.type(getSelector("27R"), "Hello", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 7R").value !== "Hello"`
+            `${getQuerySelector("27R")}.value !== "Hello"`
           );
 
-          let text = await page.$eval("#\\32 7R", el => el.value);
+          let text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HELLO");
 
-          await page.type("#\\32 7R", " world", { delay: 100 });
+          await page.type(getSelector("27R"), " world", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 7R").value !== "HELLO world"`
+            `${getQuerySelector("27R")}.value !== "HELLO world"`
           );
 
-          text = await page.$eval("#\\32 7R", el => el.value);
+          text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HELLO WORLD");
 
           await page.keyboard.press("Backspace");
           await page.keyboard.press("Backspace");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 7R").value !== "HELLO WORLD"`
+            `${getQuerySelector("27R")}.value !== "HELLO WORLD"`
           );
 
-          text = await page.$eval("#\\32 7R", el => el.value);
+          text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HELLO WOR");
 
-          await page.type("#\\32 7R", "12.dL", { delay: 100 });
+          await page.type(getSelector("27R"), "12.dL", { delay: 100 });
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 7R").value !== "HELLO WOR"`
+            `${getQuerySelector("27R")}.value !== "HELLO WOR"`
           );
 
-          text = await page.$eval("#\\32 7R", el => el.value);
+          text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HELLO WORDL");
 
-          await page.type("#\\32 7R", " ", { delay: 100 });
+          await page.type(getSelector("27R"), " ", { delay: 100 });
 
           await page.keyboard.down("Control");
           await page.keyboard.press("Backspace");
           await page.keyboard.up("Control");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 7R").value !== "HELLO WORDL "`
+            `${getQuerySelector("27R")}.value !== "HELLO WORDL "`
           );
 
-          text = await page.$eval("#\\32 7R", el => el.value);
+          text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HELLO ");
 
-          await page.$eval("#\\32 7R", el => {
+          await page.$eval(getSelector("27R"), el => {
             // Select LL
             el.selectionStart = 2;
             el.selectionEnd = 4;
           });
 
           await page.keyboard.press("a");
-          text = await page.$eval("#\\32 7R", el => el.value);
+          text = await page.$eval(getSelector("27R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("HEAO ");
         })
       );
@@ -1232,12 +1245,12 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.type("#\\32 8R", "Hello", { delay: 100 });
+          await page.type(getSelector("28R"), "Hello", { delay: 100 });
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 8R").value !== "123"`
+            `${getQuerySelector("28R")}.value !== "123"`
           );
 
-          let text = await page.$eval("#\\32 8R", el => el.value);
+          let text = await page.$eval(getSelector("28R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("Hello123");
 
           // The action will trigger a calculateNow which itself
@@ -1246,11 +1259,11 @@ describe("Interaction", () => {
           await page.click("[data-annotation-id='31R']");
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 8R").value !== "Hello123"`
+            `${getQuerySelector("28R")}.value !== "Hello123"`
           );
 
           // Without preventing against infinite loop the field is empty.
-          text = await page.$eval("#\\32 8R", el => el.value);
+          text = await page.$eval(getSelector("28R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("123");
         })
       );
@@ -1261,7 +1274,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue14705.pdf", "#\\32 9R");
+      pages = await loadAndWait("issue14705.pdf", getSelector("29R"));
       pages.map(async ([, page]) => {
         page.on("dialog", async dialog => {
           await dialog.dismiss();
@@ -1280,23 +1293,23 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          await page.type("#\\32 9R", "Hello World", { delay: 100 });
-          await page.click("#\\32 7R");
+          await page.type(getSelector("29R"), "Hello World", { delay: 100 });
+          await page.click(getSelector("27R"));
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 9R").value !== "Hello World"`
+            `${getQuerySelector("29R")}.value !== "Hello World"`
           );
 
-          let text = await page.$eval("#\\32 9R", el => el.value);
+          let text = await page.$eval(getSelector("29R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("checked");
 
-          await page.click("#\\32 7R");
+          await page.click(getSelector("27R"));
 
           await page.waitForFunction(
-            `document.querySelector("#\\\\32 9R").value !== "checked"`
+            `${getQuerySelector("29R")}.value !== "checked"`
           );
 
-          text = await page.$eval("#\\32 9R", el => el.value);
+          text = await page.$eval(getSelector("29R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("unchecked");
         })
       );
@@ -1307,7 +1320,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("bug1766987.pdf", "#\\37 5R");
+      pages = await loadAndWait("bug1766987.pdf", getSelector("75R"));
     });
 
     afterAll(async () => {
@@ -1321,16 +1334,16 @@ describe("Interaction", () => {
             "window.PDFViewerApplication.scriptingReady === true"
           );
 
-          let text = await page.$eval("#\\37 5R", el => el.value);
+          let text = await page.$eval(getSelector("75R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("150.32 €");
 
-          text = await page.$eval("#\\38 2R", el => el.value);
+          text = await page.$eval(getSelector("82R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("12.74 Kwh");
 
-          text = await page.$eval("#\\39 1R", el => el.value);
+          text = await page.$eval(getSelector("91R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("352.19 Kwh");
 
-          text = await page.$eval("#\\31 01R", el => el.value);
+          text = await page.$eval(getSelector("101R"), el => el.value);
           expect(text).withContext(`In ${browserName}`).toEqual("20.57 €");
         })
       );
@@ -1341,7 +1354,7 @@ describe("Interaction", () => {
     let pages;
 
     beforeAll(async () => {
-      pages = await loadAndWait("issue15053.pdf", "#\\34 4R");
+      pages = await loadAndWait("issue15053.pdf", getSelector("44R"));
     });
 
     afterAll(async () => {
@@ -1371,7 +1384,7 @@ describe("Interaction", () => {
             .withContext(`In ${browserName}`)
             .toEqual("visible");
 
-          await page.click("#\\34 4R");
+          await page.click(getSelector("44R"));
 
           visibility = await page.$eval(
             "[data-annotation-id='35R']",
diff --git a/test/integration/test_utils.js b/test/integration/test_utils.js
index b94eecfbc..1a5d529e1 100644
--- a/test/integration/test_utils.js
+++ b/test/integration/test_utils.js
@@ -58,3 +58,18 @@ exports.clearInput = async (page, selector) => {
   await page.keyboard.up("Control");
   await page.keyboard.press("Backspace");
 };
+
+function getSelector(id) {
+  return `[data-element-id="${id}"]`;
+}
+exports.getSelector = getSelector;
+
+function getQuerySelector(id) {
+  return `document.querySelector('${getSelector(id)}')`;
+}
+exports.getQuerySelector = getQuerySelector;
+
+function getComputedStyleSelector(id) {
+  return `getComputedStyle(${getQuerySelector(id)})`;
+}
+exports.getComputedStyleSelector = getComputedStyleSelector;
diff --git a/web/pdf_scripting_manager.js b/web/pdf_scripting_manager.js
index 32a2f1732..68c3a50e7 100644
--- a/web/pdf_scripting_manager.js
+++ b/web/pdf_scripting_manager.js
@@ -351,7 +351,9 @@ class PDFScriptingManager {
 
     const ids = siblings ? [id, ...siblings] : [id];
     for (const elementId of ids) {
-      const element = document.getElementById(elementId);
+      const element = document.querySelector(
+        `[data-element-id="${elementId}"]`
+      );
       if (element) {
         element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
       } else {