En la línea 24 hice el siguiente cambio en parametro de la función para xpath
//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]
Cambié j por i. Solo eso.
Conocer el ecosistema de Selenium
Por qué aprender Selenium y qué verás
Historia de Selenium
Otras herramientas de testing y automatización
Preparar entorno de trabajo
Configurar entorno de trabajo
Compatibilidad con Python 3.9 y aprendiendo a utilizar múltiples versiones
¡Hola, mundo!
Utilizar comandos básicos
Encontrar elementos con find_element
Preparar assertions y test suites
Entender las clases WebDriver y WebElement
Interactuar con elementos
Manejar form, textbox, checkbox y radio button
Manejar dropdown y listas
Manejar alert y pop-up
Automatizar navegación
Sincronizar pruebas
Demora implícita y explícita
Condicionales esperadas
Retos
Agregar y eliminar elementos
Elementos dinámicos
Controles dinámicos
Typos
Ordenar tablas
Metodologías de Trabajo
Data Driven Testing (DDT)
Page Object Model (POM)
Cierre del curso
Realizar una prueba técnica
Conclusiones
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Aportes 55
Preguntas 3
En la línea 24 hice el siguiente cambio en parametro de la función para xpath
//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]
Cambié j por i. Solo eso.
El objetivo de la clase era aprender a obtener la información de una tabla y se cumplió. Sin embargo, la información almacenada en la colección, quedó mal estructurada a mi parecer.
Este es el resultado del ejercicio del docente:
>>> [['Last Name', 'Smith', 'Frank', '[email protected]', '$50.00'], ['First Name', 'Smith', 'Frank', '[email protected]', '$50.00'], ['Email', 'Smith', 'Frank', '[email protected]', '$50.00'], ['Due', 'Smith', 'Frank', '[email protected]', '$50.00'], ['Web Site', 'Smith', 'Frank', '[email protected]', '$50.00']]
Por cada columna, está guardando la información de toda la fila.
Esta debería ser el resultado correcto, a mi parecer:
>>> [['Last Name', 'Smith', 'Bach', 'Doe', 'Conway'], ['First Name', 'John', 'Frank', 'Jason', 'Tim'], ['Email', '[email protected]', '[email protected]', '[email protected]', '[email protected]'], ['Due', '$50.00', '$51.00', '$100.00', '$50.00']]
NOTA: Modifiqué las url del código anterior, porque no me dejaba publica este comentario con dichas urls.
Ahora, el inicio de cada sublista tiene el nombre de la columna y el resto de posiciones está la información de solo esa columna.
El problema es como obtiene el valor de row_data. Dejo el código corregido para que puedan ver las diferencias:
import unittest
from selenium import webdriver
class Typos(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(5)]
print(table_data)
for i in range(5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
Lo realize de la siguiente manera:
import unittest
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
class Tables(unittest.TestCase):
def setUp(self):
"""Start web driver"""
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument('--headless')
options.add_argument('--disable-gpu')
self.driver = webdriver.Chrome(options=options)
def tearDow(self):
"""Stop web driver"""
self.driver.quit()
def test_sort_tables(self):
driver = self.driver
table_data = []
try:
"""Find and click link with text Sortable Data Tables"""
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
get_rows_table = driver.find_element_by_id('table1').get_property('rows')
get_head_table = get_rows_table[0].get_property('cells')
for i in range(1, len(get_rows_table)):
data = {}
for j in range(len(get_rows_table)):
get_head_cells = get_head_table[j].text
get_body_cells = get_rows_table[i].get_property('cells')[j].text
data.update({get_head_cells:get_body_cells})
table_data.append(data)
print(table_data)
except NoSuchElementException as ex:
self.fail(ex.msg)
if __name__ == "__main__":
unittest.main(verbosity = 2)```
Obteniendo el siguiente resultado:
[{'Last Name': 'Smith', 'First Name': 'John', 'Email': '[email protected]', 'Due': '$50.00', 'Web Site': 'http://www.jsmith.com'}, {'Last Name': 'Bach', 'First Name': 'Frank', 'Email': '[email protected]', 'Due': '$51.00', 'Web Site': 'http://www.frank.com'}, {'Last Name': 'Doe', 'First Name': 'Jason', 'Email': '[email protected]', 'Due': '$100.00', 'Web Site': 'http://www.jdoe.com'}, {'Last Name': 'Conway', 'First Name': 'Tim', 'Email': '[email protected]', 'Due': '$50.00', 'Web Site': 'http://www.timconway.com'}]
¿Qué les parece esta presentación?
Aquí les dejo como lo hice con PrettyTable:
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from prettytable import PrettyTable
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(r'chromedriver')
driver = self.driver
#Aquí pongan el enlace, como no es https no me permite añadir mi comentario.
driver.get()
def test_table(self):
driver = self.driver
rows = []
ptable = PrettyTable()
for i in range(5):
header = driver.find_element(
By.XPATH, f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
for j in range(4):
row = driver.find_element(
By.XPATH, f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]')
rows.append(row.text)
ptable.add_column(header.text, rows)
rows.clear()
print(ptable)
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
En este enlace encontrarán la info para usar PrettyTable.
El resultado del video no es correcto. min 6.10. No se vuelcan correctamente los datos de la lista
Reto resuelto:
def test_sort_tables(self):
driver = self.driver
# Total elementos del header a capturar
header_data_size = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th')) -1
# total elementos del body a capturar.
body_data_size = len(driver.find_elements_by_css_selector('#table1 > tbody > tr'))
# Se crea una lista de sublistas
table_data = [[] for i in range(body_data_size)]
# Se capturan los datos
for i in range(body_data_size):
for j in range(header_data_size):
header_data = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{j+1}]/span').text
cell_data = driver.find_element_by_xpath(f'//*[@id="table2"]/tbody/tr[{i+1}]/td[{j+1}]').text
table_data[i].append({header_data : cell_data})
print(table_data)
Resultado:
[[{'Last Name': 'Smith'}, {'First Name': 'John'}, {'Email': '[email protected]'}, {'Due': '$50.00'}, {'Web Site':
'https://www.jsmith.com'}], [{'Last Name': 'Bach'}, {'First Name': 'Frank'}, {'Email': '[email protected]'}, {'Due': '$51.00'}, {'Web Site': 'https://www.frank.com'}], [{'Last Name': 'Doe'}, {'First Name': 'Jason'}, {'Email': '[email protected]'}, {'Due': '$100.00'}, {'Web Site': 'https://www.jdoe.com'}], [{'Last Name': 'Conway'}, {'First Name': 'Tim'}, {'Email': '[email protected]'}, {'Due': '$50.00'}, {'Web Site': 'https://www.timconway.com'}]]```
Lo más lindo de Python, además de ser Open Source, son sus librerías. Con esta van a poder visualizar de manera mucho más estética los valores obtenidos de la tabla:
https://pypi.org/project/prettytable/
Además de jugar un poco más con el código 😃 Espero que les guste!
Deberían regrabar esta clase, ese ciclo for está otro nivel de mal 😦
Si se fijan el codigo de la clase retorna la misma fila de la tabla varias veces, dejo mi codigo con las pequeñas correcciones
import unittest
from selenium import webdriver
from time import sleep
ROWS = 5
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome('../chromedriver')
driver = self.driver
driver.get('pagina de prueba') #no la pongo porque la IA de comentarios de platzi me la borra
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for _ in range(ROWS)]
for i in range(ROWS):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table_data[i].append(header.text)
for j in range(ROWS - 1):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity = 2)
RESULTADO:
test_sort_tables (__main__.Tables) ...
DevTools listening on ws://127.0.0.1:50420/devtools/browser/1ba7a561-8b24-40af-9638-e8594d558ec8
[['Last Name', 'Smith', 'Bach', 'Doe', 'Conway'], ['First Name', 'John', 'Frank', 'Jason', 'Tim'], ['Email', 'jsmith@gmail.com', 'fbach@yahoo.com', 'jdoe@hotmail.com', 'tconway@earthlink.net'], ['Due', '$50.00', '$51.00', '$100.00', '$50.00'], ['Web Site', 'https://www.jsmith.com', 'https://www.frank.com', 'https://www.jdoe.com', 'https://www.timconway.com']]
Acá lo hice con diccionario, listo para convertirlo en DataFrame si se desea:
def test_sort_tables(self):
driver = self.driver
table_data = {}
print(table_data)
for i in range(5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table_data[header.text] = []
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[header.text].append(row_data.text)
print(table_data)
Hola, acá mi aporte, tiene el extra de que determina el número de filas y columnas de la tabla, esto lo hace através de los selectores css.
def test_sort_tables(self):
driver = self.driver
# Compute rows and columns
rows = len(driver.find_elements_by_css_selector('#table1 > tbody > tr'))
cols = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th'))
table = [[] for i in range(rows+1)]
# Add headers
for i in range(rows):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table[0].append(header.text)
# Add table data
for i in range(rows):
for j in range(cols):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i + 1}]/td[{j + 1}]')
table[i].append(row_data.text)
# Let's give it a basic format
formated_rows = [', '.join(i) for i in table]
print('\n' + '\n'.join(formated_rows))
En mi humilde opinion con el selector CSS se puede hacer mas rapido, siplemente pones la tabla y ya, nada de for loop.
En pantalla mi respuesta:
import unittest
from selenium import webdriver
from pyunitreport import HTMLTestRunner
from selenium.webdriver.common.by import By
class HomePageTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome(executable_path = 'chromedriver.exe')
cls.driver.get('https://the-internet.herokuapp.com/tables')
cls.driver.maximize_window()
cls.driver.implicitly_wait(15)
def test_xfil(self):
"""Test of table by its xpath"""
data_table = self.driver.find_element(By.CSS_SELECTOR, '#table1')
print(data_table.text)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity = 2,
testRunner = HTMLTestRunner(output = 'reportes', report_name = 'table-data-report'))
Aquí está mi resolución del reto, espero a alguien le sea de ayuda.
Con este código logré que devuelva, en cada lista creada (creadas en base a la cantidad de filas que tiene la tabla) en forma de diccionarios, cada dato del header junto al que contiene el body.
El código está todo comentado para que se entienda mejor.
import unittest
from selenium import webdriver
from time import sleep
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path=r'C:\chromedriver.exe')
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
# Get the len of the head data ( Table 1 ), getting a list of elements from his css selector ( -1 for the edit/delete panel )
columns_size = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th')) - 1
# Get the len of data size ( in rows ) from the table 1 ( Without header )
row_size = len(driver.find_elements_by_css_selector('#table1 > tbody > tr'))
table_data = [[] for i in range(row_size)] # Create a list with empty lists inside (the quantity of rows of the body)
print(table_data)
# Lets save all data
for i in range(row_size): # Iter through each row of the data
# Now iter through each column of the data
for j in range(columns_size):
# Take each head data text of the table
header_data = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{j + 1}]/span').text # j will iter column by column
# Take each body data text
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i + 1}]/td[{j + 1}]').text # i iter row by row
# Append to the table data, as a dict
table_data[i].append({header_data : row_data})
print(table_data)
def tearDown(self):
self.driver.close()
if __name__ == '__main__':
unittest.main(verbosity = 2)
Esto es lo que devuelve el código.
esta clase esta bastante improvisada, las iteraciones le salen mal y el lo sabe, y no hace nada por corregirlas
El reto queda así:
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
COLUMN_NUMBER = 5
ROW_NUMBER = 4
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path="./usr/bin/chromedriver")
driver = self.driver
driver.get("https://the-internet.herokuapp.com/")
driver.find_element(By.LINK_TEXT, "Sortable Data Tables").click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(COLUMN_NUMBER)]
print(table_data)
for i in range(COLUMN_NUMBER):
header = driver.find_element(
By.XPATH, f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span'
)
table_data[i].append(header.text)
for j in range(ROW_NUMBER):
row_data = driver.find_element(
By.XPATH, f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]'
)
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
A quién le sirva, el siguiente código extrae los datos de header y body de forma aislada
y genera un dataframe de pandas 😃
def test_sort_tables(self):
driver = self.driver
header = driver.find_elements(By.XPATH, '//*[@id="table1"]/thead/tr/th')
header_text = [cell.text for cell in header[:-1]]
body_table = driver.find_elements(By.XPATH, '//*[@id="table1"]/tbody/tr')
table = []
for row in body_table:
values = row.find_elements(By.XPATH, "td")
values = [cell.text for cell in values[:-1]]
table.append(values)
table_df = pd.DataFrame(data=table, columns=header_text)
print(table_df)
el código para instalar pandas es
pip install pandas
Con el ordenamiento de la clase los datos quedan escalonados, se cambio la linea row_data = driver.find_element(By.XPATH,f’//*[@id=“table1”]/tbody/tr[{j + 1}]/td[{i + 1}]’) para que conserve los datos de la columna y se itere solo las filas y queda asi, ya sale la columna web site con los datos de sus filas:
[[‘Last Name’, ‘Smith’, ‘Bach’, ‘Doe’, ‘Conway’], [‘First Name’, ‘John’, ‘Frank’, ‘Jason’, ‘Tim’], [‘Email’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’], [‘Due’, ‘$50.00’, ‘$51.00’, ‘$100.00’, ‘$50.00’], [‘Web Site’, aqui sales las urls que no deja aportar en el comentario’]]
Considerando la organizacion correcta de los datos, actualizando el codigo actualizado. (deprecated) El codigo final quedaria asi.
import unittest
from selenium import webdriver
from prettytable import PrettyTable
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.maximize_window()
driver.implicitly_wait(3)
driver.find_element(by=By.LINK_TEXT, value='Sortable Data Tables').click()
def test_sort_table(self):
driver = self.driver
table_data = [[] for i in range(5)]
print(table_data)
ptable = PrettyTable()
for i in range(5):
header = driver.find_element(by=By.XPATH, value=f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element(by=By.XPATH, value=f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]')
table_data[i].append(row_data.text)
ptable.add_column(header.text, table_data[i])
#print(table_data)
print(ptable)
def tearDown(self):
self.driver.implicitly_wait(3)
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity = 2)
Hice el reto utilizando list comprehension y dict comprehension y asi creo una lista de diccionarios como un API retorna los datos de base de datos.
def test_sort_tables(self):
driver = self.driver
table_data = []
header_elements = driver.find_elements(
By.CSS_SELECTOR, '#table1 > thead > tr > th.header')
header_list = [
header.text for header in header_elements if not header.text == 'Action']
rows_elements = driver.find_elements(
By.CSS_SELECTOR, '#table1 > tbody > tr')
for row in rows_elements:
field_elements = row.find_elements(By.CSS_SELECTOR, 'td')
row_data = {header_list[i]: field_elements[i].text
for i in range(len(header_list))}
table_data.append(row_data)
print('\n', table_data)
Usé este código para imprimir la tabla con PrettyTable
pip3 install prettytable
from prettytable import PrettyTable
table = PrettyTable()
for column in table_columns:
table.add_column(column[0],column)
table.del_row(0)
print(table)
Yo lo hice con diccionarios para mas placer
def test_b_sort_tables(self):
link = self.driver.find_element(
By.XPATH, '/html/body/div[2]/div/ul/li[41]/a')
link.click()
data_list = []
list_len = self.driver.find_elements(
By.XPATH, '//*//table[@id="table1"]/tbody/tr')
for i in range(len(list_len)):
info = self.driver.find_elements(
By.XPATH, f'//*//table[@id="table1"]/tbody/tr[{i+1}]/td')
user_info = [x.text for x in info]
# de aqui en adelante es para poner los valores en diccionarios
dict = {"Last Name": "", "First Name": "",
"Email": "", "Due": "", "Web Site": ""}
dict.update({"Last Name": user_info[0]})
dict.update({"First Name": user_info[1]})
dict.update({"Email": user_info[2]})
dict.update({"Due": user_info[3]})
dict.update({"Web Site": user_info[4]})
data_list.append(dict)
print(data_list)
Para no tener que hacer maromas para tener los datos de cada columna con su table_header, use un dictionary con listas y no solo listas con más listas.
#!/usr/bin/python
import unittest
from selenium import webdriver
class Tests (unittest.TestCase):
def setUp (self):
self.options = webdriver.ChromeOptions()
self.options.binary_location = '/usr/bin/brave'
self.driver = webdriver.Chrome(executable_path='/home/bl4ky113/bin/chromedriver', options=self.options)
self.driver.maximize_window()
self.driver.get("https://the-internet.herokuapp.com/tables")
def test_get_table_info (self):
categories = len(self.driver.find_elements_by_css_selector('#table1 > thead > tr > th')) - 1
size_table = len(self.driver.find_elements_by_css_selector('#table1 > tbody > tr'))
table = {}
for i in range(categories):
category = self.driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span').text
table[category] = []
for j in range(size_table):
table[category].append(self.driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]').text)
print(table)
def tearDown (self):
self.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity=2)
Dejo mi version para que en cada sub-lista aparezca la data de cada usuario, si hay algo que pueda mejorar o corregir son bien aceptadas:
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver = self.driver
driver.maximize_window()
driver.get('https://the-internet.herokuapp.com/')
driver.find_element(By.LINK_TEXT, 'Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
iter_rows = range(1, 5)
iter_colums = range(1, 6)
table_data = [[] for i in range(4)]
for row in iter_rows:
for colum in iter_colums:
data = driver.find_element(By.XPATH, f'//*[@id="table1"]/tbody/tr[{row}]/td[{colum}]')
table_data[row - 1].append(data.text)
print(table_data)
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
La parte del scraping está bastante desordenada. Les dejo mi versión. Cree una lista de diccionarios, así la información queda mucho más ordenada y más fácil de visualizar.
Creo que en cada array dentro de la variable table_data
deben ir las columnas como tal. Dejo mi código que ordena según ese criterio.
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
class TablesTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.driver = webdriver.Chrome(executable_path=r'./chromedriver.exe')
cls.driver.get('https://the-internet.herokuapp.com/tables')
cls.driver.maximize_window()
def test_tables(self):
table_data = [[] for i in range(5)]
headers = self.driver.find_elements(By.CLASS_NAME, 'header')
print(len(headers))
for i in range(5):
table_data[i].append(headers[i].find_element(By.TAG_NAME, 'span').text)
datos = self.driver.find_element(By.TAG_NAME, 'tbody').find_elements(By.TAG_NAME, 'tr')
for i in range(5):
for j in range(4):
table_data[i].append(datos[j].find_elements(By.TAG_NAME,'td')[i].text)
print(table_data)
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
import unittest
from selenium import webdriver
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path = './chromedriver')
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(5)]
print(table_data)
for i in range (5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity = 2)
Aquí mi aproximación al ejercicio, la cual creo que es la solución a la que quería llegar el profesor.
import unittest
from selenium import webdriver
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path= r'/mnt/c/Users/luism/OneDrive/Desktop/chromedriver.exe')
driver = self.driver
driver.get("link") # ingresa el link
driver.find_element_by_link_text('Sortable Data Tables').click()
driver.maximize_window()
def test_sort_tables(self):
driver = self.driver
table_data = {}
for i in range(5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table_data[header.text] = []
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i+1}]')
table_data[header.text].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity = 2)
el tr[ {j + 1} ] es un error de logica , necesitas iterar sobre el tr[1] para sacar todos los nombres , en cambio te estas llendo a otros campos antes de tiempo, antes de sacar todos los nombres , cuidado ahi
deberian regrabar la clase , los datos de salida no tienen ningun sentido , el codigo tiene errores de logica
code
import unittest
from pyunitreport import HTMLTestRunner
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class CompareProducts(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path='./chromedriver')
driver = self.driver
driver.implicitly_wait(30)
driver.maximize_window()
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(5)]
print(table_data)
for i in range(5):
header =driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{j+1}]')
table_data[j].append((row_data.text))
print(table_data)
def tearDown(self):
self.driver.close()
if __name__ == '__main__':
unittest.main(verbosity=2, testRunner= HTMLTestRunner(output= 'reports', report_name= 'hello-world-report'))
🤠
import unittest
from selenium import webdriver
from time import sleep
class Tables(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome(executable_path = './chromedriver')
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
return super().setUp()
def test_data_table(self):
driver = self.driver
self.assertTrue('tables'in driver.current_url)
table_data = [[] for i in range(5)]
print(table_data)
for i in range(len(table_data)):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]')
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self) -> None:
self.driver.close()
return super().tearDown()
if __name__=='__main__':
unittest.main(verbosity = 2)
from selenium import webdriver
import unittest
class SortedTables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome('..\chromedriver.exe')
driver = self.driver
driver.maximize_window()
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_tables(self):
driver = self.driver
table_data = [[] for i in range(5)]
print(table_data)
for i in range(5):
header = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{j+1}]/td[{i+1}]')
table_data[i].append(row_data.text)
print(table_data)
def tearDown(self):
self.driver.close()
if _name_ == "_main_":
unittest.main(verbosity=2)
Este es mi aporte con selectores de css, eso si yo quería visualizar los datos distintos, ni como lo ve el profe, ni como un json (es decir, { “name” : [“a”, “b”, …] , …}, o algo de ese estilo), sino la misma tabla pero en una array bidimensional, donde las rows serían efectivamente las rows por instancia, y las columnas serían los vectores, o atributos, que definen a la instancia.
def test_sort_tables(self):
driver = self.driver
n, m = 5, 6
myTable = [[0 for j in range(m)] for i in range(n)]
# header
headers = driver.find_elements_by_css_selector('#table1 > thead > tr > th > span')
for i in range(len(headers)):
myTable[0][i] = headers[i].text
# data
for i in range(1, n):
for j in range(m):
data_i = driver.find_element_by_css_selector(f"#table1 > tbody > tr:nth-child({i}) > td:nth-child({j+1})")
myTable[i][j] = data_i.text
print(myTable)
sleep(5)
El resultado sería algo del estilo:
Aqui mi versión antes de ver la solución
import unittest
from pyunitreport import HTMLTestRunner
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class ExtractData(unittest.TestCase):
@classmethod
def setUp(cls):
options = Options()
options.binary_location = '/snap/brave/119/opt/brave.com/brave/brave'
cls.driver = webdriver.Chrome(options = options, executable_path="./chromedriver")
driver = cls.driver
driver.get("https://the-internet.herokuapp.com/tables")
driver.maximize_window()
def test_get_data(self):
driver = self.driver
header = driver.find_elements_by_css_selector("#table1 thead th")
column_names = [c.text for c in header]
rows = driver.find_elements_by_css_selector("#table1 tbody tr")
data = [{"id":i} for i,row in enumerate(rows)]
for i, row in enumerate(rows):
vls = row.find_elements_by_css_selector("td")
for k, v in zip(column_names,vls):
data[i][k] = v.text
print(data)
@classmethod
def tearDown(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity=2, testRunner=HTMLTestRunner(output="reportes", report_name="automatic_search"))
Lista ordenada (posicion 0 es el titulo de la columna):
for i in range(5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for i in range(5):
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j +1}]/td[{i+1}]')
table_data[i].append(row_data.text)
print(table_data)
Adjunto el código donde el resultado corresponderá a cada fila de la tabla.
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(ROWS)]
print()
for fila in range(ROWS):
for columna in range(COLUMNS):
if fila == 0:
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{columna + 1}]/span')
table_data[fila].append(header.text)
else:
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{fila}]/td[{columna + 1}]')
table_data[fila].append(row_data.text)
print(table_data[fila])
El resultado por consola fue el siguiente:
En el video muestra siempre la misma primera línea en la impresión en la consola, debe ser algo con los for y la variable que se coloca en el XPath.
Este es mi código:
"""
Este scrip obtiene el header de una tabla y su contenido
"""
import unittest
from selenium import webdriver
class Tables(unittest.TestCase):
def setUp(self):
self.webdriver = webdriver.Chrome(executable_path='../chromedriver_win32/chromedriver.exe')
self.webdriver.get('iserte la url aca')
self.webdriver.maximize_window()
self.webdriver.implicitly_wait(1)
self.webdriver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
webdriver = self.webdriver
table_data = list()
# Obtengo una lista de los tr en el body de la tabla
# a dicha lista le aplico un len() y obtenermos cuantos datos hay en el body
data_size = webdriver.find_elements_by_xpath('//*[@id="table1"]/tbody/tr')
# Obtener una lista de los th contenido en el thead de la tabla
# Con la lista de los th, le aplico un len a la lista y veos cuantos header hay
header_size = webdriver.find_elements_by_xpath('//*[@id="table1"]/thead/tr/th')
for i in range(1,len(data_size)+1):
for j in range(1,len(header_size)): # Omitimos el header action
header = webdriver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{j}]/span').text
content = webdriver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i}]/td[{j}]').text
table_data.append({header:content})
print(table_data)
def tearDown(self):
self.webdriver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
La salida:
Pista reto revisa los xpath
El problema de los magic number lo podemos solucionar usando una variable llamada colums = 5
como ejemplo y remplazamos el 5 por dicha variable
Reto cumplido:
def test_sort_tables(self):
driver = self.driver
list_of_elements = [[] for i in range(5)]
print(list_of_elements)
for i in range(5):
element = driver.find_element_by_xpath('//*[@id="table1"]/thead/tr/th[{}]/span'.format(i+1))
list_of_elements[0].append(element.text)
for i in range(1,5):
for j in range(5):
element = driver.find_element_by_xpath('//*[@id="table1"]/tbody/tr[{}]/td[{}]'.format(i,j+1))
list_of_elements[i].append(element.text)
for i in range(5):
print(list_of_elements[i])
Un pequeño scrapping de la primera tabla:
def test_tables(self):
driver = self.driver
person = {}
big_list = []
keys = []
i = 1; j = 1; x = 1
y = 0; r = 0
while True:
try:
header = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i}]/span')
keys.append(header.text)
i += 1
except:
break
while True:
try:
driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{x}]/td[1]')
while True:
try:
item = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{x}]/td[{j}]')
person[keys[y]] = item.text
j += 1; y += 1
except:
break
big_list.append(person.copy())
print(big_list)
j = 1; x += 1; y = 0; r += 1
except:
break
import unittest
from selenium import webdriver
from api_data_mock import ApiDataMock
from selenium.webdriver.support.ui import Select # Modulo para poder seleccionar del dropdown
from time import sleep
from selenium.webdriver.common.by import By #Hacer referencia a un elemento del sitio web, para interactuar
from selenium.webdriver.support.ui import WebDriverWait # Modulo para manejo de esperas explicitas
from selenium.webdriver.support import expected_conditions as EC #esperas explicitas
class Tables(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(executable_path = r'./chromedriver.exe')
driver = self.driver
# driver.implicitly_wait(30)
driver.maximize_window()
driver.get('*************************') #colocar el sitio web
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
COLUMNS = 5
FILES = 4
table_data = [[] for i in range(COLUMNS)]
print(table_data)
for i in range(COLUMNS):
# table_data[i].append(header.text)
for j in range (FILES):
header = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i + 1}]/span')
row_data = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[j].append({header.text : row_data.text})
print(table_data)
def tearDown(self):
self.driver.implicitly_wait(5)
self.driver.close()
if __name__ == '__main__':
unittest.main(verbosity=2)
Creo que en el código del video se están recorriendo varias veces los datos (una por cada columna de la tabla), porque está el for anidado con el otro, en realidad no es necesario.
Esta es mi solución
driver = self.driver
table = [ ]
for i in range(1,6):
column = [ ]
header_xpath = f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i}]/span'
header = driver.find_element_by_xpath(header_xpath)
column.append(header.text)
for j in range(1, 5):
cell_xpath = f'/html/body/div[2]/div/div/table[1]/tbody/tr[{j}]/td[{i}]'
cell = driver.find_element_by_xpath(cell_xpath)
column.append(cell.text)
table.append(column)
pongo al xpath como una variable para evitar lineas muy largas, porque si no el linter me jode.
y remplace el +1 en el xpath por subir 1 el inicio y el fin del rango.
fuera de eso lo unico que hice es que el segundo for muestre los datos que corresponden a nuestro interes
La tabla está incorrecta y es por este pequeño detalle:
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[i].append(row_data.text)
En vez de usar j dos veces, primero usamos j y después i.
Así obtuve esta tabla:
[
[‘Last Name’, ‘Smith’, ‘Bach’, ‘Doe’, ‘Conway’],
[‘First Name’, ‘John’, ‘Frank’, ‘Jason’, ‘Tim’],
[‘Email’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’, ‘[email protected]’],
[‘Due’, ‘$50.00’, ‘$51.00’, ‘$100.00’, ‘$50.00’],
[‘Web Site’, ‘https://www.jsmith.com’, ‘https://www.frank.com’, ‘https://www.jdoe.com’, ‘https://www.timconway.com’]
]
Y listo, ya tuvimos todos los datos de la tabla acomodados en sus columnas y filas correspondientes.
hola a todos, el resultado obtenido por el profesor no es el correcto, si se fijan bien, por cada columna esta entregando los valores de la una sola fila de la tabla, el único valor que cambio fue el nombre del header, lo demás esta repetido en cada lista: ‘Smith’, ‘Frank’, ‘[email protected]’, ‘50’ esta secuencia se repite en todas, además los datos no son los correspondientes, puesto el nombre del seño Smith no es Frank, sino John,… el error esta en los ciclos usando los iteradores, por el momento les comparto mi solución que da el resultado esperado, sin usar ciclos anidados e imprime un datagrama con la ayuda de pandas
Yo lo hice de esta forma, me gusta un poco mas ya que los muestra en forma de tabla o similar. Nose si era el objetivo de la clase o si esta hecho de la mejor forma. Soy nuevo en esto, pero estoy satisfecho con lo que quedo. Saludos a todos!
import unittest
from selenium import webdriver
class Tables(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome(executable_path=r'C:\Users\ignac\OneDrive\Escritorio\Selenium con Python\chromedriver.exe')
driver = self.driver
driver.get('https://the-internet.herokuapp.com/')
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sort_tables(self):
driver = self.driver
table_data = [[] for i in range(5)]
for i in range(5):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')
table_data[0].append(header.text)
g = 1
while g < 5:
for j in range(5):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{g}]/td[{j+1}]')
table_data[g].append(row_data.text)
g += 1
for o in range(5):
print(table_data[o])
def tearDown(self) -> None:
self.driver.close()
if __name__ == '__main__':
unittest.main(verbosity=2)
lo realice de esta manera:
import unittest
from selenium import webdriver
from time import sleep
class oerdenar_tabla(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome(executable_path=r'C:/chromedriver.exe')
driver = cls.driver
driver.maximize_window()
driver.get('######')
driver.implicitly_wait(15)
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_prueba1(self):
driver = self.driver
data = [[] for i in range(5)]
headers = driver.find_elements_by_xpath('//*[@id="table1"]/thead/tr/th/span')
for i in range(0, 5):
data[0].append(headers[i].text)
print('\n')
print(data[0])
for i in range(1,5):
for j in range(0,5):
dato = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i}]/td[{j+1}]').text
data[i].append(dato)
print(data[i])
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
Salida:
['Last Name', 'First Name', 'Email', 'Due', 'Web Site']
['Smith', 'John', '[email protected]', '$50.00', '#######']
['Bach', 'Frank', '[email protected]', '$51.00', '#######']
['Doe', 'Jason', '[email protected]', '$100.00', '#######']
['Conway', 'Tim', '[email protected]', '$50.00', '#######']
#agregue "######" en lugar de los enlaces para que me dejara subirlo
Saludos chicos aqui les dejo el codigo de la clase con algunos comentarios:
import unittest
from selenium import webdriver
from time import sleep
class SortablaData(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox(executable_path= r'./geckodriver.exe')
driver = self.driver
driver.get("https://the-internet.herokuapp.com/")
driver.find_element_by_link_text('Sortable Data Tables').click()
def test_sortable_data(self):
driver = self.driver
columnas = 5
filas = 4
#Lista vacia que contiene almacenara los datos
table_data = [[] for i in range(columnas)]#Cinco por la cantidad de columnass que existen
print(table_data)#Impresion de valores
#Iteracion por headers y informacion de las tablas
for i in range(columnas):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span')#Copiamos el Xpath por que permite iterar sobre el
table_data[i].append(header.text)#Ingresamos los datos a la tabla vacia
for j in range(filas): #Ciclo para recorrer filas
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]')
table_data[i].append(row_data.text)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main()
Dejo mi aporte.
def test_data(self):
driver = self.driver
WebDriverWait(driver, 15).until(EC.title_contains('The Internet'))
headers = [] #Obteniendo los header(Last name, First name...)
for i in range(5):
get_header = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i + 1}]')
headers.append(get_header.text)
cells = [] #Obteniendo los valores de los headers en sublistas([Smith,Jhon,jsmith@...], [], etc)
for i in range(4):
cells_block = []
for j in range(5):
get_cells = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{i + 1}]/td[{j + 1}]')
cells_block.append(get_cells.text)
cells.append(cells_block)
data = {} #Fusionando todo en un diccionario con diccionarios dentro que contienen la info de cada persona.
for i in range(4):
user_data = {}
for j in range(5):
user_data.update({headers[j] : cells[i][j]})
data.update({f'Persona{i + 1}': user_data})
print(data)```
for i in range(6):
header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i+1}]/span')
table_data[i].append(header.text)
for j in range(4):
row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]')
table_data[i].append(row_data.text)
print(table_data)
listo reto cumplido!!
cada clase más interesante u.u
Brillante
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?