Biomathematicus

Science, Technology, Engineering, Art, Mathematics

If you have been using Dropbox for many years, you may have accumulated hundreds or even thousands of shared links. At some point, you might decide to clean them up. Dropbox provides a page for this purpose: 👉 https://www.dropbox.com/share/links Unfortunately, the manual process is extremely tedious.

For each shared link, the workflow is roughly:

  1. Click the three-dot menu next to the link.
  2. Click Delete view link or Delete edit link.
  3. Confirm the action in a modal dialog by clicking Delete link.

This means three clicks per link. If you have hundreds of links, the process can take hours. I wanted to automate this task.

I describe next the process I took to find a solution. The final solution is in Step 5 below.


Step 1 — Providing the HTML Source to an AI Agent

The first step was to inspect the page using the browser’s developer tools. The page is built using a modern JavaScript framework and contains a complex DOM structure. For example, the menu button looks roughly like this:

<button data-testid="open-menu-button">
   <svg>...</svg>
</button>

Clicking that button dynamically renders a dropdown menu with options such as:

  • Delete view link
  • Delete edit link

Those options are not present in the HTML until the menu opens, which complicates automation. To help the AI understand the interface, I provided a snippet of the HTML source from the table row containing the menu button. The goal was to generate a small JavaScript script that could run in the browser console and simulate the required clicks.


Step 2 — First Attempt

The initial idea was straightforward:

for (var index = 0; index < 1000; index++) {
   var dots = document.getElementById("bubbleDropdownTarget-" + index);
   if (dots == undefined) continue;

   dots.click();
   document.getElementsByClassName("bubble-menu-item")[0].click();
   document.getElementsByClassName("button-primary dbmodal-button")[0].click();
}

This approach assumed:

  • predictable element IDs
  • static menu elements
  • synchronous DOM updates

None of these assumptions were correct. The script failed because the interface is React-based and renders elements dynamically.


Step 3 — Discovering the Real Structure

The next iteration involved examining the markup more carefully.

Key observations:

  • The three-dot button is reliably identifiable using
    data-testid="open-menu-button".
  • The dropdown menu items are rendered with
    role="menuitem".
  • The confirmation dialog contains a button labeled
    “Delete link.”

Another complication was that the dropdown menu is rendered in a portal attached to the <body> element, not inside the table row. This meant the script needed to:

  1. Click the menu button.
  2. Wait for the dropdown menu to render.
  3. Find the delete option.
  4. Click it.
  5. Wait for the modal dialog.
  6. Click Delete link.

Step 4 — Handling Timing

Another failure point was timing. The UI updates asynchronously, so the script must pause briefly between steps. Without those pauses, the code attempts to click elements that do not yet exist. The solution was to introduce a small helper function:

function sleep(ms){
  return new Promise(r => setTimeout(r, ms));
}

This allows the script to wait for UI elements to appear.


Step 5 — Final Working Script

The final version looks like this:

function sleep(ms){
  return new Promise(r => setTimeout(r, ms));
}

async function deleteAllLinks(){

  const menus = document.querySelectorAll('[data-testid="open-menu-button"]');

  for (let i = 0; i < menus.length; i++){

    console.log("Processing", i+1, "/", menus.length);

    menus[i].click();
    await sleep(700);

    let deleteItem = [...document.querySelectorAll('[role="menuitem"]')]
      .find(el =>
        el.innerText &&
        (el.innerText.includes("Delete view link") ||
         el.innerText.includes("Delete edit link"))
      );

    if(!deleteItem){
      console.log("Delete option not found");
      document.body.click();
      await sleep(400);
      continue;
    }

    deleteItem.click();
    await sleep(700);

    let confirmBtn = [...document.querySelectorAll("button")]
      .find(b => b.innerText && b.innerText.includes("Delete link"));

    if(confirmBtn){
      confirmBtn.click();
    }

    await sleep(900);
  }

  console.log("Finished");
}

deleteAllLinks();

Running this in the browser console automates the entire deletion process.


Lessons Learned

Several insights emerged during the process.

1. Modern Web Interfaces Are Dynamic. Elements often do not exist until the user interacts with the page. Automation scripts must account for this.

2. Stable Attributes Are Critical. Attributes such as data-testid are often the most reliable selectors.

3. Timing Matters. JavaScript frameworks render elements asynchronously, so small delays are necessary.

4. Iteration Is Key. The working solution emerged through several cycles:

  1. naive static DOM approach
  2. improved element targeting
  3. handling React portals
  4. adding timing delays
  5. refining selectors

Final Thoughts

This small automation illustrates an interesting workflow:

  1. Inspect a complex interface.
  2. Provide the HTML structure to an AI agent.
  3. Iterate through progressively better scripts.
  4. Refine until the automation reliably interacts with the UI.

What began as a multi-hour manual task was ultimately reduced to a few seconds of automated execution after a few minutes of directed AI agent interactions. This post was the last step of the agent’s work; I had to edit it.

For anyone managing large numbers of shared links in Dropbox, this approach can save a significant amount of time.