Waiting for all spinners to disappear

I want to wait for all spinners to disappear.
After clicking a specific button, the application displays 3 spinners. I observed behavior:

  • when app is slow: spinner 1 -> spinner 2 -> spinner 3
  • when app is quick: spinner 1 -> spinner 3 (perhaps because spinner 2 ended before spinner 1 ended)

How do I make sure that all 3 spinners disappeared?

I tried
self.wait.until(expected_conditions.invisibility_of_element(element))
for all 3 spinners consecutively, but it sometimes fails

best solution your element should look for ALL 3 spinners.

1 how you search now for each of them ?
2 ios / android ?

Thanks Aleksei,

Ad 1.

  1. Click the button preceding spinners with
    driver.find_element(*locator).click()
  2. Expect first spinner disappears
    wait.until(expected_conditions.invisibility_of_element(first_spinner))
  3. Expect second spinner disappears
    wait.until(expected_conditions.invisibility_of_element(second_spinner))
  4. Expect third spinner disappears
    wait.until(expected_conditions.invisibility_of_element(third_spinner))

I think better suitable would be some method like .invisibility_of_elements(elements), but it is not available (I am using Appium-Python-Client = “2.1.4”)

Ad 2.
This is Android.

write all 3 locators

Those are the locators:

SPINNER_BUSY = (AppiumBy.ACCESSIBILITY_ID, "Busy")
SPINNER_ACCEPT = (AppiumBy.ACCESSIBILITY_ID, "Accept")
SPINNER_SUCCESS = (AppiumBy.ACCESSIBILITY_ID, "Success")

And then I wait for all of them:

wait.until(expected_conditions.invisibility_of_element(SPINNER_BUSY))
wait.until(expected_conditions.invisibility_of_element(SPINNER_ACCEPT))
wait.until(expected_conditions.invisibility_of_element(SPINNER_SUCCESS))

with Appium java annotations it is simple:

    @AndroidFindAll(value = {
            @AndroidBy(accessibility = "Busy"), 
            @AndroidBy(accessibility = "Accept"),
            @AndroidBy(accessibility= "Success")
    })
    private WebElement spinnerElement;

without try something like:


driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().textMatches(\"Busy|Accept|Success\")"));

Thanks, I will try that out soon. Does this return a list of elements? What happens if one of the spinners is present but the others are not?
I am not familiar with androidUIAutomator("new…

in mine example it return first one whatever found. but! you can change it to return list of elements (if it needed)

https://developer.android.com/reference/androidx/test/uiautomator/UiSelector

Ah, ok, so I should do something like this (changed your reply slightly - I hope it will work in Python):

spinner = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR("new UiSelector().textMatches(\"Busy|Accept|Success\")"))
wait.until(expected_conditions.invisibility_of_element(spinner))
spinner = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR("new UiSelector().textMatches(\"Busy|Accept|Success\")"))
wait.until(expected_conditions.invisibility_of_element(spinner))
spinner = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR("new UiSelector().textMatches(\"Busy|Accept|Success\")"))
wait.until(expected_conditions.invisibility_of_element(spinner))

only this should be enough

OK, one more question - will this work when text is different than accessibility ID? I see I am supposed to use UiSelector().textMatches(“Busy|Accept|Success”)") but those are IDs in the app and not texts.

for ids just change to -> resourceIdMatches

wait = WebDriverWait(self.driver, 10)
spinner = self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR("newUiSelector().resourceIdMatches(\"Busy|Accept|Success\")"))
wait.until(expected_conditions.invisibility_of_element(spinner))

Test fails:

2022-10-07 23:36:18:104 [Appium] Received SIGTERM - shutting down
2022-10-07 23:36:18:104 [Appium] Cleaning up 1 active session
2022-10-07 23:36:18:105 [Appium] Closing session, cause was 'The process has received SIGTERM signal'
2022-10-07 23:36:18:105 [Appium] Removing session 'ae0c3f3d-7770-469c-a68f-2ac5b8308891' from our master session list
2022-10-07 23:36:18:105 [UiAutomator2] Deleting UiAutomator2 session
2022-10-07 23:36:18:105 [UiAutomator2] Deleting UiAutomator2 server session
2022-10-07 23:36:18:105 [WD Proxy] Matched '/' to command name 'deleteSession'
2022-10-07 23:36:18:105 [WD Proxy] Proxying [DELETE /] to [DELETE http://127.0.0.1:8200/wd/hub/session/44026040-b710-4b4c-b900-d4d9a2132a21] with no body
2022-10-07 23:36:18:111 [HTTP] --> DELETE /wd/hub/session/ae0c3f3d-7770-469c-a68f-2ac5b8308891
2022-10-07 23:36:18:111 [HTTP] {}
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)] Encountered internal error running command: NoSuchDriverError: A session is either terminated or not started
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at asyncHandler (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/appium-base-driver/lib/protocol/protocol.js:243:15)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at /Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/appium-base-driver/lib/protocol/protocol.js:423:15
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at Layer.handle [as handle_request] (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at next (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/route.js:137:13)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at Route.dispatch (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/route.js:112:3)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at Layer.handle [as handle_request] (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at /Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:281:22
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at param (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:354:14)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at param (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:365:14)
2022-10-07 23:36:18:122 [W3C (ae0c3f3d)]     at Function.process_params (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:410:3)
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at next (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:275:10)
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at logger (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/morgan/index.js:144:5)
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at Layer.handle [as handle_request] (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/layer.js:95:5)
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at trim_prefix (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:317:13)
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at /Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:284:7
2022-10-07 23:36:18:123 [W3C (ae0c3f3d)]     at Function.process_params (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/express/lib/router/index.js:335:12)
2022-10-07 23:36:18:123 [HTTP] <-- DELETE /wd/hub/session/ae0c3f3d-7770-469c-a68f-2ac5b8308891 404 12 ms - 2350
2022-10-07 23:36:18:123 [HTTP] 
2022-10-07 23:36:18:126 [WD Proxy] Got response with status 200: {"sessionId":"44026040-b710-4b4c-b900-d4d9a2132a21","value":null}
2022-10-07 23:36:18:126 [ADB] Running '/usr/local/share/android-commandlinetools/platform-tools/adb -P 5037 -s 5200f0c48c0cc5db shell am force-stop [redacted]'
2022-10-07 23:36:18:252 uncaughtException: write EPIPE
Error: write EPIPE
    at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16)
2022-10-07 23:36:18:253 uncaughtException: write EPIPE
Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:160:15)
    at writeGeneric (node:internal/stream_base_commons:151:3)
    at Socket._writeGeneric (node:net:817:11)
    at Socket._write (node:net:829:8)
    at writeOrBuffer (node:internal/streams/writable:389:12)
    at _write (node:internal/streams/writable:330:10)
    at Socket.Writable.write (node:internal/streams/writable:334:10)
    at Console.log (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston/lib/winston/transports/console.js:51:25)
    at Console._write (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/index.js:103:17)
    at doWrite (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:428:64)
    at writeOrBuffer (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:417:5)
    at Console.Writable.write (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:334:11)
    at DerivedLogger.ondata (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:681:20)
    at DerivedLogger.emit (node:events:539:35)
    at addChunk (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:298:12)
    at readableAddChunk (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:280:11)
2022-10-07 23:36:18:254 uncaughtException: write EPIPE
Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:160:15)
    at writeGeneric (node:internal/stream_base_commons:151:3)
    at Socket._writeGeneric (node:net:817:11)
    at Socket._write (node:net:829:8)
    at writeOrBuffer (node:internal/streams/writable:389:12)
    at _write (node:internal/streams/writable:330:10)
    at Socket.Writable.write (node:internal/streams/writable:334:10)
    at Console.log (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston/lib/winston/transports/console.js:51:25)
    at Console._write (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/index.js:103:17)
    at doWrite (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:428:64)
    at writeOrBuffer (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:417:5)
    at Console.Writable.write (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/winston-transport/node_modules/readable-stream/lib/_stream_writable.js:334:11)
    at DerivedLogger.ondata (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:681:20)
    at DerivedLogger.emit (node:events:539:35)
    at addChunk (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:298:12)
    at readableAddChunk (/Users/[redacted]/.nvm/versions/node/v16.15.1/lib/node_modules/appium/node_modules/readable-stream/lib/_stream_readable.js:280:11)

better switch to invisibilityOfElementLocated​ in such case it will start looking for all 3. while in your code it finds first spinner as element and looks only for it.

plus add your page source to check

It is the same I think.
In expected_conditions.py there is:

def invisibility_of_element(element):
    """ An Expectation for checking that an element is either invisible or not
    present on the DOM.

    element is either a locator (text) or an WebElement
    """
    return invisibility_of_element_located(element)

but it will miss the case when 2 spinners are present first and only after 2 end, 3rd is displayed?

i feel you do not understand what you are writing. lets look at it closer:

CODE 1

// here tap some button and after spinners start appear

// now you write find element of first spinner and as result it will find first element spinner if it on screen
WebElement first_spinner = driver.findElement(AppiumBy.Accessibility = "Busy")
// now you write second element of second spinner and as result it will find NO ELEMENT while first spinner on screen
WebElement second_spinner = driver.findElement(AppiumBy.Accessibility = "Accept")
// now you write third element of third spinner and as result it will find NO ELEMENT while first spinner on screen
WebElement fhird_spinner = driver.findElement(AppiumBy.Accessibility = "Success")

// finally you start waiting them to disappear
// first spinner on screen visible and we wait it disappear
wait.until(expected_conditions.invisibility_of_element(first_spinner))
// now you trying to wait disappear of NO element
wait.until(expected_conditions.invisibility_of_element(second_spinner))
// now you trying to wait disappear of NO element
wait.until(expected_conditions.invisibility_of_element(third_spinner))

to make your code a bit more reliable you need to fix order:
CODE 2

// here tap some button and after spinners start appear

// find first spinner element and wait it disappear
WebElement first_spinner = driver.findElement(AppiumBy.Accessibility = "Busy")
wait.until(expected_conditions.invisibility_of_element(first_spinner))

// find second spinner element and wait it disappear
WebElement second_spinner = driver.findElement(AppiumBy.Accessibility = "Accept")
wait.until(expected_conditions.invisibility_of_element(second_spinner))

// now third spinner code should be clear

I suggested you instead of finding element and then wait it disappear use invisibilityOfElementLocated. how it should look now your code:
CODE 3

// here tap some button and after spinners start appear

// try find and wait to disappear first spinner
wait.until(expected_conditions. invisibilityOfElementLocated(AppiumBy.Accessibility = "Busy"))

// try find and wait to disappear second spinner
wait.until(expected_conditions. invisibilityOfElementLocated(AppiumBy.Accessibility = "Accept"))

// try find and wait to disappear third spinner
wait.until(expected_conditions. invisibilityOfElementLocated(AppiumBy.Accessibility = "Success"))

see now code 2 times less.

finally i suggested to use complex locator to cover all 3 spinners:
CODE 4

// here tap some button and after spinners start appear

// try find and wait to disappear ANY spinner
wait.until(expected_conditions. invisibilityOfElementLocated(AppiumBy. androidUIAutomator = "uiSelector covering all 3 spinners"))

PS important update! invisibility_of_element WILL NOT WORK if you finding elements with:

WebElement first_spinner = driver.findElement(AppiumBy.Accessibility = "Busy")

cause element already found and it will not change any more. so CODE 1 and CODE 2 I wrote incorrectly.
to work with invisibility_of_element you need use pageObject model where EVERY use of element search for it.

CODE 1 (CORRECTED)

// first declare elements. not sure how for JS

@AndroidFindBy(AppiumBy.Accessibility = "Busy")
WebElement first_spinner;
@AndroidFindBy(AppiumBy.Accessibility = "Accept")
WebElement second_spinner;

// here tap some button and after spinners start appear

// first spinner on screen visible and we wait it disappear (inside it Appium automatically first tries to find element first and pass it to `invisibility_of_element` function)
wait.until(expected_conditions.invisibility_of_element(first_spinner))
// second spinner on screen visible and we wait it disappear
wait.until(expected_conditions.invisibility_of_element(second_spinner))
// third spinner on screen visible and we wait it disappear
wait.until(expected_conditions.invisibility_of_element(third_spinner))

Thanks Aleksei and sorry for responding so late (I totally forgot I posted).
I tried out your 3rd proposition - works wonderfully. We should mark your comment as the correct answer (if that’s possible) and close the topic.