Continuando a parlare di Python e Web testing, in questa lezione ci poniamo due obbiettivi:
- iniziamo a fare pratica con l'interrogazione di pagine web mettendo a frutto quanto appreso finora;
- approfondiamo l'importantissimo argomento del waiting ovvero la capacità di Selenium di attendere che appaia il contenuto della pagina o almeno la parte che ci interessa.
Perché aspettare?
Le pagine web, con il tempo, sono diventate sempre più vive e dinamiche. Tecnologie lato client basate sul linguaggio Javascript e approcci come il ben noto Ajax, le hanno rese in grado di generare dinamicamente contenuto, senza fare un reload totale, ma scaricando dati o producendone. La velocità di Selenium lo porterà a leggere direttamente il contenuto della pagina non appena questa apparirà, in un momento in cui il contenuto di cui abbiamo bisogno potrebbe ancora non essere del tutto disponibile. Il waiting è proprio quella tecnica che ci permette di attendere che i dati di cui abbiamo bisogno siano pronti.
Esperimenti della lezione: pagina con e senza attesa
L'esperimento che faremo sarà quello di usare due pagine web, una chiamata paginasenzaattesa.html
in cui apparirà subito un messaggio in un tag h1
ed un'altra, paginaconattesa.html
, in cui il tag h1
sarà aggiunto da uno script JQuery dopo 5 secondi.
Ricordiamo che le pagine web per essere invocabili devono essere raggiungibili via HTTP pertanto disponibili su un server web, anche locale. Le opzioni sono varie. Le si può esporre on line su un server a propria disposizione, lanciare da un server web locale offerto da pacchetti per lo sviluppo web o, visto che abbiamo Python sotto mano, utilizzare il server web che questo linguaggio già offre di suo.
Per seguire quest'ultima strada, sarà sufficiente salvare le pagine web che ci interessano in una cartella e dall'interno di questa lanciare il comando:
$ python -m http.server 80
per averle disponibili alla porta TCP/80 dell'indirizzo http://localhost
.
Caso 1: pagina senza attesa
La pagina paginasenzaattesa.html
è la seguente:
<!DOCTYPE html>
<html>
<head>
<title>Pagina senza attesa</title>
</head>
<body>
<h1>Messaggio che appare subito</h1>
</body>
</html>
e il seguente script Python (noi usiamo Chrome ma si scelga il driver che si preferisce):
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('http://localhost/paginasenzaattesa.html')
titolo=driver \
.find_element(By.TAG_NAME, "h1")
print(f"Trovato il contenuto: {titolo.text}")
driver.quit()
genera come output la stringa
Trovato il contenuto: Messaggio che appare subito
Pertanto, tutto ha funzionato. Come richiamo alle lezioni precedenti, possiamo dire che come tecnica abbiamo usato la ricerca per tag, ideale in un caso in cui dobbiamo individuare l'unico elemento di un tipo specifico nella pagina.
Caso 2: pagina con attesa
La seconda pagina HTML, paginaconattesa.html
, è vuota inizialmente e dopo 5 secondi viene popolata da uno script Javascript che aggiunge al body
un elemento h1
con un titolo:
<!DOCTYPE html>
<html>
<head>
<title>Pagina senza attesa</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<script>
$().ready(()=> {
setTimeout(
function()
{
$("body").append("<h1>Messaggio che appare dopo 5 secondi!</h1>")
}, 5000)
})
</script>
</body>
</html>
L'approccio del paragrafo precedente qui non funzionerebbe in quanto cercherebbe di leggere subito il testo che in realtà non è stato ancora prodotto. Dobbiamo quindi suggerire a Selenium di attendere il tempo necessario. Un primo approccio, a volte usato ma sconsigliabile, è quello di mettere un tempo di attesa usando il codice in puro Python:
import time
time.sleep(5)
ma, ben conscio della problematica, Selenium aggiunge degli strumenti appositi che impongono al driver di monitorare la situazione per un timeout massimo. Esistono due versioni.
L'approccio implicito imposta un timeout per il driver in questo modo:
driver = webdriver.Firefox()
driver.implicitly_wait(10)
dove il valore 10 è il timeut in secondi.
Alternativa a questo, ciò che proviamo noi nell'esempio, è l'approccio esplicito in cui viene impostato un timeout in attesa di un evento come l'apparizione del tag HTML che stiamo aspettando. Nel caso in cui il timeout scatta viene lanciata l'eccezione TimeoutException
e quindi il tutto può essere gestito con il costrutto try...except...finally
. Nello script seguente, il driver aspetta massimo 10 secondi attendendo che appaia un oggetto con tag h1
:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
driver = webdriver.Chrome()
driver.get('http://localhost/paginaconattesa.html')
secondi= 10
try:
titolo = WebDriverWait(driver, secondi).until(
EC.visibility_of_element_located((By.TAG_NAME, "h1")))
print(f"Trovato il messagio: {titolo.text}")
except TimeoutException:
print(f"Trascorso il timeout di {secondi} secondi ma non è apparso nulla")
finally:
driver.quit()
I 10 secondi saranno sufficienti per attendere che un elemento di tag h1
appaia facendo scattare l'evento visibility_of_element_located
e, aspetto interessante, l'operazione di attesa restituisce direttamente l'oggetto titolo
che rappresenta la componente appena apparsa: da lì stamperemo il testo che contiene.
Da notare che la chiusura del driver viene posta nel finally
in quanto qualsiasi sia l'esito dell'esperimento il browser aperto da Selenium dovrà essere chiuso.
Come approfondimento ulteriore, che attualmente esula dai nostri interessi, si consideri che esistono altre condizioni da utilizzare oltre all'acquisita visibilità ed è prevista anche la possibilità di configurare le proprie condizioni.