You just discovered that QTP does not offer any explicit support for synchronizing with asynchronous browser script execution like that of AJAX-driven websites. When QTP believes the page has been fully loaded, there actually are still JavaScript handlers running, possibly updating the HTML used for the page and QTP accesses the GUI to early.
readyState
is a good idea, but usually, it is easy to find cases where that does not work good enough.
1. The best solution is to synchronize on a "busy" indicator of the application, like a progress bar, or activity indicator.
Unfortunately, waiting for a busy indicator implies the busy indicator does really appear, always, but many apps show one only if the process takes long enough (longer than 2 seconds, or the like). Then, this quickly becomes a bit messier than expected.
2. If the app does not have anything like this, often you might help yourself by synchronizing on some "ready" indicator, like "an expected field appeared", or "The OK button disappeared". This often requires a specific solution for every context if there is no real "ready" indicator (which usually does not exist).
3. In many projects, the automation folks can get a busy indicator built into the application just for them. While this does not create a lot of effort for the developers (because modern applications have a central message dispatcher so the transition for "busy" to "idle" state and vv can easily be tracked centrally), it greatly simplifies the amount of work required for synchronization.
So if possible, try to contact the developers and get them present a property (variable, memory-mapped file, semaphore, whatever they prefer) which the test robot "synch" routine can easily poll. (Hint: To be able to differentiate between two "ready" states even after "missing" the "busy" state between the two, it might be helpful to get a sequential "busy state count" in addition to a "busy status flag", so you might request that on the same occasion.) Then, all synch issues are a defect in the app since it obviously did not maintain the ready signal correctly.
Update For apps that are based on a de facto "standard" framework, one might find ways to implement synchronization in a generic way.
For example, for JavaScript applications, I managed to create an instrumentation that transparently reports the flow of events to QTP, which is used there to wait "just long enough", enabling one to set special checkpoint-like library calls that wait for certain events (especially "click", and for apps that do AJAX roundtripls a la Java Server Pages, "ajaxstop", events) to be completed before continueing.
This has proved to be extremely useful, because often, it is very complicated to get dev to implement any kind of support for test automation needs, and GUI-based synchronization (solely through test object state/existance) sometimes is not enough if the app performs asynchronuous requests in the background. It also eliminates the need to explore synchronization options for each and every GUI context, which can be extremely time-consuming and/or unreliable.