In this post
- TikTok: the time picker that jumps by three
- X: the toolbar that changes height
- Meta: the click that opens a system dialog
- The pattern
- The filename follows you around
- The three rules that survived
- 1. Never reuse a coordinate across calls
- 2. Verify in a call separate from the action
- 3. Wait for conditions, never for time
- What this has to do with building apps
- The honest scoreboard
After fixing the two YouTube bugs — covered in part 1 — we expected the rest to be routine. TikTok, X, Instagram, Facebook, Kwai and Reddit were left.
Every platform produced a new failure. And every new failure, looked at closely, was the same failure.
TikTok: the time picker that jumps by three
TikTok's scheduler does not accept a typed time. It is a scroll picker with behaviour nobody documents:
- clicking the last visible row of the hour column advances +3 hours
- the value actually selected is the middle row, not the row you clicked
- the dropdown's position on screen varies with available space
So you click "18" and select "15". Or "21". Depending on where the dropdown decided to open.
Video 15 got scheduled for 18:10 instead of 18:00. The exact reason: the verification had been issued in the same call as the click. The response described the state before the click took effect, and we read that response as confirmation.
Verifying and acting in one call is the same mistake as reading a value and using it after another thread wrote to it. Here the other thread is the UI.
The minutes have their own logic: with the hour on the middle row, "00" lands on the first row, so that click is direct with no scrolling.
TikTok also has a quirk that is not a bug at all: the first upload always fails. Not sometimes — always. The console states the reason plainly:
web upload is under Vmok mode
selectVideoFiles-start does not exist
It is an anti-automation guard, and there is no clever way around it: reloading the page and retrying works, every time. What changes is how you treat it in the procedure. While we treated it as an error, every video became an investigation. Once we wrote "reload after the first failure" as step 2 of the recipe, the cost went to zero.
A deterministic failure is not a failure: it is a step that has not been written into the procedure yet. The real expense was not the rework — it was re-diagnosing the same thing fifteen times.
X: the toolbar that changes height
On X the schedule button lives in the composer toolbar, which changes height in three situations:
| Composer state | Button position |
|---|---|
| One line of text | y = 312 |
| Two lines of text | y = 337 |
| Link preview card loaded | y = 625 |
Three different values, and the third only appears seconds later, once X finishes fetching the preview. A coordinate captured before the card loads points at the wrong place by the time the click happens.
And that is not all: the window resizes on its own between actions. Nothing that depends on absolute position survives that.
Meta: the click that opens a system dialog
The most interesting case was Meta's video editor. The visible upload button opens the operating system's native file dialog — outside the browser, and therefore out of reach of any page automation.
The fix was not clicking better. It was understanding what the button does: it calls click() on a <input type="file"> the application creates on the fly. With that element located, the file goes straight to it — no system dialog involved.
When a click opens a system window, page automation ends there. The answer is not to keep clicking: it is to find the element the click triggers.
The pattern
| Platform | What moves | What breaks |
|---|---|---|
| YouTube | Title field grows as you type | The description click lands in the title |
| TikTok | Dropdown opens up or down | Silently selects the wrong hour |
| X | Toolbar shifts when the preview loads | The schedule click misses |
| Meta | Upload element is created on click | The system dialog blocks everything |
| Grid changes after upload | "Next" moves | |
| Editor toggles markdown / rich text | Text lands in the wrong field |
Six interfaces, six different reasons, one problem: between the moment you read the screen and the moment you click, the screen changed.
The filename follows you around
One coincidence is worth isolating, because it is not a coincidence. On YouTube the title reverted to the filename. On TikTok the description arrives pre-filled with the filename — and on videos 9 and 10 it ignored the first typing and kept the original value. Clicking the field again and repeating fixed it; the second attempt sticks.
Two unrelated platforms, two different fields, the same symptom. The shared cause lives in neither YouTube nor TikTok: it is a field with a default value that the framework restores whenever the new write is not recognised as state. The filename is simply the most common default in the world of upload forms.
That changes what you look for. The question is not "why does this platform erase my text", it is "what value does this screen consider true when it doubts me".
The three rules that survived
1. Never reuse a coordinate across calls
A coordinate has a shelf life measured in milliseconds. Either you locate the element and click it, or you read its position in the same action that clicks. Storing (694, 337) for the next step is storing a pointer to freed memory.
2. Verify in a call separate from the action
This is the rule that cost the most to learn and paid the most. Checking and clicking together produces a reading of the past. Confirm, then act.
3. Wait for conditions, never for time
wait 3 seconds is a guess. wait until the footer says "Checks complete" is a contract. Every time the process depended on a fixed delay, it failed on a larger video or a slower connection.
- YouTube: wait for the footer text to change
- TikTok: the first upload attempt always fails with an anti-automation
guard; reloading and retrying works. That is a condition, not a delay.
- X: the schedule dialog is not in the accessibility tree for the first few
seconds even though it is visibly open; repeating the query resolves it
What this has to do with building apps
Everything, which is why this post sits in the engineering track.
The same three rules describe UI testing, and they explain why end-to-end tests have a reputation for flakiness. A test that waits on time, stores coordinates and asserts on state it does not control is flaky by construction — and the tooling takes the blame unfairly.
The short version, good for both:
Do not assert anything about a screen you did not just read. And do not read a screen in the same action that modifies it.
The honest scoreboard
None of those is 100%, and the reasons are not technical: TikTok only allows scheduling 30 days ahead, and Meta enforces an upload rate limit that shows up as an upload stuck at 0% — no error, no warning, no network request. Waiting is the only fix.
That is engineering information too. Half the work of publishing at scale is not publishing: it is finding where each platform puts the brake and stopping hitting it.
In part 3: a post that looked published, appeared on our profile, and existed for nobody else.
Frequently asked questions
Why not always use element selectors instead of coordinates?
Because it is not always possible. Some components only respond to a real pointer event — clicking the element merely focuses it. In those cases the rule is to read the element's position at click time, never to reuse a coordinate captured earlier.
Which rule finally stabilised the process?
Separating verification from action. While we checked and clicked in the same call, the check described a state that no longer existed when the click landed. Verify, then act in the next call.
Does this apply to UI testing in general?
It does, and it is the same lesson as end-to-end testing: assert on state you control, wait for conditions rather than time, and never carry screen positions between steps. The difference here is that the UI belongs to someone else and changes without notice.