artespy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- artespy/__init__.py +3 -0
- artespy/errors/__init__.py +22 -0
- artespy/selenium/handler.py +262 -0
- artespy/selenium/locators.py +96 -0
- artespy/selenium/pages/base.py +34 -0
- artespy/selenium/pages/components/base.py +8 -0
- artespy/selenium/pages/components/navbar.py +40 -0
- artespy/selenium/pages/home.py +28 -0
- artespy/selenium/pages/login.py +47 -0
- artespy/selenium/pages/mixins/table.py +105 -0
- artespy/selenium/pages/processo.py +378 -0
- artespy-0.1.0.dist-info/METADATA +10 -0
- artespy-0.1.0.dist-info/RECORD +15 -0
- artespy-0.1.0.dist-info/WHEEL +4 -0
- artespy-0.1.0.dist-info/entry_points.txt +2 -0
artespy/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class HandlerException(Exception):
|
|
2
|
+
"""Classe base para exceptions relacionadas ao handler."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UnknownAlertException(HandlerException):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ElementNotFoundException(HandlerException):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidPageException(HandlerException):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SeiException(Exception):
|
|
18
|
+
"""Classe base para exceptions relacionadas ao SEI."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InvalidCredentialsError(SeiException):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import os
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from selenium.webdriver import Chrome, ChromeOptions, ChromeService
|
|
7
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
8
|
+
from webdriver_manager.chrome import ChromeDriverManager
|
|
9
|
+
|
|
10
|
+
from artespy.errors import HandlerException, InvalidPageException
|
|
11
|
+
from artespy.selenium.pages.home import HomePage
|
|
12
|
+
from artespy.selenium.pages.login import LoginPage
|
|
13
|
+
from artespy.selenium.pages.processo import ProcessoPage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SeleniumHandler:
|
|
17
|
+
"""Handler Selenium para automatizar operações no portal SEI."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, headless: bool = False, timeout: int = 60) -> None:
|
|
20
|
+
self.headless: bool = headless
|
|
21
|
+
self.timeout: int = timeout
|
|
22
|
+
|
|
23
|
+
self.driver = None
|
|
24
|
+
self.wait = None
|
|
25
|
+
self._page = None
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def page(self):
|
|
29
|
+
if not self._page:
|
|
30
|
+
raise HandlerException("Página atual não inicializada.")
|
|
31
|
+
return self._page
|
|
32
|
+
|
|
33
|
+
def _inicializar_driver(self) -> None:
|
|
34
|
+
"""Instanciar um novo driver e espera explícita."""
|
|
35
|
+
opts = ChromeOptions()
|
|
36
|
+
opts.add_argument("--log-level=3")
|
|
37
|
+
opts.add_argument("--start-maximized")
|
|
38
|
+
if self.headless:
|
|
39
|
+
opts.add_argument("--headless")
|
|
40
|
+
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
|
|
41
|
+
opts.add_experimental_option("useAutomationExtension", False)
|
|
42
|
+
opts.add_experimental_option(
|
|
43
|
+
"prefs",
|
|
44
|
+
{
|
|
45
|
+
"credentials_enable_service": False,
|
|
46
|
+
"profile.password_manager_enabled": False,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
chrome_install = ChromeDriverManager().install()
|
|
51
|
+
folder = os.path.dirname(chrome_install)
|
|
52
|
+
driver_path = os.path.join(folder, "chromedriver.exe")
|
|
53
|
+
svc = ChromeService(driver_path)
|
|
54
|
+
|
|
55
|
+
self.driver = Chrome(service=svc, options=opts)
|
|
56
|
+
self.wait = WebDriverWait(self.driver, self.timeout)
|
|
57
|
+
|
|
58
|
+
def _encerrar_driver(self) -> None:
|
|
59
|
+
if self.driver:
|
|
60
|
+
self.driver.close()
|
|
61
|
+
self.driver = None
|
|
62
|
+
if self.wait:
|
|
63
|
+
self.wait = None
|
|
64
|
+
|
|
65
|
+
def __enter__(self) -> "SeleniumHandler":
|
|
66
|
+
"""Ponto de entrada do gerenciador de contexto."""
|
|
67
|
+
self._inicializar_driver()
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
71
|
+
"""Ponto de saída do gerenciador de contexto."""
|
|
72
|
+
self._encerrar_driver()
|
|
73
|
+
|
|
74
|
+
def _get_driver_e_espera(
|
|
75
|
+
self,
|
|
76
|
+
) -> tuple[Chrome, WebDriverWait[Chrome]]:
|
|
77
|
+
if not self.driver or not self.wait:
|
|
78
|
+
raise HandlerException("Driver e/ou espera não inicializados.")
|
|
79
|
+
return self.driver, self.wait
|
|
80
|
+
|
|
81
|
+
def acessar_sei(self, usuario: str, senha: str, orgao: str = "ARTESP"):
|
|
82
|
+
"""Acessa e faz login no portal SEI."""
|
|
83
|
+
driver, wait = self._get_driver_e_espera()
|
|
84
|
+
|
|
85
|
+
if not isinstance(usuario, str):
|
|
86
|
+
msg = "Acessando SEI: usuario deve ser uma string"
|
|
87
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
88
|
+
if not usuario:
|
|
89
|
+
msg = "Acessando SEI: usuário deve ser uma string não vazia"
|
|
90
|
+
raise ValueError(msg)
|
|
91
|
+
if not isinstance(senha, str):
|
|
92
|
+
msg = "Acessando SEI: senha deve ser uma string"
|
|
93
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
94
|
+
if not senha:
|
|
95
|
+
msg = "Acessando SEI: senha deve ser uma string não vazia"
|
|
96
|
+
raise ValueError(msg)
|
|
97
|
+
if not isinstance(orgao, str):
|
|
98
|
+
msg = "Acessando SEI: orgao deve ser uma string"
|
|
99
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
100
|
+
if not orgao:
|
|
101
|
+
msg = "Acessando SEI: órgão deve ser uma string não vazia"
|
|
102
|
+
raise ValueError(msg)
|
|
103
|
+
|
|
104
|
+
LoginPage(driver, wait).fazer_login(usuario, senha, orgao)
|
|
105
|
+
self._page = HomePage(driver, wait)
|
|
106
|
+
self._page.fechar_modal_novidades()
|
|
107
|
+
|
|
108
|
+
def trocar_unidade(self, unidade: str):
|
|
109
|
+
driver, wait = self._get_driver_e_espera()
|
|
110
|
+
|
|
111
|
+
if not isinstance(unidade, str):
|
|
112
|
+
msg = "Trocando unidade: unidade deve ser uma string"
|
|
113
|
+
raise TypeError(msg) # pyright:ignore[reportUnreachable]
|
|
114
|
+
if not unidade:
|
|
115
|
+
msg = "Trocando unidade: unidade deve ser uma string não vazia"
|
|
116
|
+
raise ValueError(msg)
|
|
117
|
+
|
|
118
|
+
self.page.navbar.trocar_unidade(unidade)
|
|
119
|
+
self._page = HomePage(driver, wait)
|
|
120
|
+
|
|
121
|
+
def buscar_processo(self, processo: str) -> bool:
|
|
122
|
+
driver, wait = self._get_driver_e_espera()
|
|
123
|
+
|
|
124
|
+
encontrado = self.page.navbar.buscar_processo(processo)
|
|
125
|
+
if encontrado:
|
|
126
|
+
self._page = ProcessoPage(driver, wait)
|
|
127
|
+
return encontrado
|
|
128
|
+
|
|
129
|
+
def ler_processos_recebidos(self) -> pd.DataFrame:
|
|
130
|
+
if not isinstance(self.page, HomePage):
|
|
131
|
+
msg = "página atual não é a página de controle de processos"
|
|
132
|
+
raise InvalidPageException(msg)
|
|
133
|
+
|
|
134
|
+
r = self.page.ler_tabela_recebidos()
|
|
135
|
+
if not r.empty:
|
|
136
|
+
r = (
|
|
137
|
+
r.drop(columns=["Unnamed: 0", "Recebidos", "Recebidos.2"])
|
|
138
|
+
.rename(columns={"Recebidos.1": "Processo"})
|
|
139
|
+
.drop_duplicates(ignore_index=True)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return r
|
|
143
|
+
|
|
144
|
+
def ler_processos_gerados(self) -> pd.DataFrame:
|
|
145
|
+
if not isinstance(self.page, HomePage):
|
|
146
|
+
msg = "página atual não é a página de controle de processos"
|
|
147
|
+
raise InvalidPageException(msg)
|
|
148
|
+
|
|
149
|
+
g = self.page.ler_tabela_gerados()
|
|
150
|
+
if not g.empty:
|
|
151
|
+
g = (
|
|
152
|
+
g.drop(columns=["Unnamed: 0", "Gerados", "Gerados.2"])
|
|
153
|
+
.rename(columns={"Gerados.1": "Processo"})
|
|
154
|
+
.drop_duplicates(ignore_index=True)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return g
|
|
158
|
+
|
|
159
|
+
def ler_processos_unidade(self) -> pd.DataFrame:
|
|
160
|
+
"""Retorna os processos recebidos e gerados em um DF."""
|
|
161
|
+
r = self.ler_processos_recebidos()
|
|
162
|
+
g = self.ler_processos_gerados()
|
|
163
|
+
return pd.concat([r, g], ignore_index=True)
|
|
164
|
+
|
|
165
|
+
def ler_status(self) -> str | list[str]:
|
|
166
|
+
if not isinstance(self.page, ProcessoPage):
|
|
167
|
+
msg = "página atual não é a página de processo"
|
|
168
|
+
raise InvalidPageException(msg)
|
|
169
|
+
return self.page.ler_status()
|
|
170
|
+
|
|
171
|
+
def ler_marcadores(self) -> list[str]:
|
|
172
|
+
if not isinstance(self.page, ProcessoPage):
|
|
173
|
+
msg = "página atual não é a página de processo"
|
|
174
|
+
raise InvalidPageException(msg)
|
|
175
|
+
return self.page.ler_marcadores()
|
|
176
|
+
|
|
177
|
+
def ler_prazo(self) -> dt.date | None:
|
|
178
|
+
if not isinstance(self.page, ProcessoPage):
|
|
179
|
+
msg = "página atual não é a página de processo"
|
|
180
|
+
raise InvalidPageException(msg)
|
|
181
|
+
return self.page.ler_prazo()
|
|
182
|
+
|
|
183
|
+
def ler_andamentos(self) -> pd.DataFrame:
|
|
184
|
+
if not isinstance(self.page, ProcessoPage):
|
|
185
|
+
msg = "página atual não é a página de processo"
|
|
186
|
+
raise InvalidPageException(msg)
|
|
187
|
+
return self.page.ler_andamentos()
|
|
188
|
+
|
|
189
|
+
def mover_documentos(self, nome: str, destino: str, motivo: str) -> None:
|
|
190
|
+
if not isinstance(self.page, ProcessoPage):
|
|
191
|
+
msg = "página atual não é a página de processo"
|
|
192
|
+
raise InvalidPageException(msg)
|
|
193
|
+
if not isinstance(nome, str):
|
|
194
|
+
msg = "nome deve ser uma string"
|
|
195
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
196
|
+
if not nome:
|
|
197
|
+
msg = "nome deve ser uma string não vazia"
|
|
198
|
+
raise ValueError(msg)
|
|
199
|
+
if not isinstance(destino, str):
|
|
200
|
+
msg = "destino deve ser uma string"
|
|
201
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
202
|
+
if not destino:
|
|
203
|
+
msg = "destino deve ser uma string não vazia"
|
|
204
|
+
raise ValueError(msg)
|
|
205
|
+
if not isinstance(motivo, str):
|
|
206
|
+
msg = "motivo deve ser uma string"
|
|
207
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
208
|
+
if not motivo:
|
|
209
|
+
msg = "motivo deve ser uma string não vazia"
|
|
210
|
+
raise ValueError(msg)
|
|
211
|
+
|
|
212
|
+
self.page.mover_documentos(nome, destino, motivo)
|
|
213
|
+
|
|
214
|
+
def concluir_processo(self) -> None:
|
|
215
|
+
if not isinstance(self.page, ProcessoPage):
|
|
216
|
+
msg = "página atual não é a página de processo"
|
|
217
|
+
raise InvalidPageException(msg)
|
|
218
|
+
self.page.concluir_processo()
|
|
219
|
+
|
|
220
|
+
def anexar_processo(self, processo: str) -> None:
|
|
221
|
+
if not isinstance(self.page, ProcessoPage):
|
|
222
|
+
msg = "página atual não é a página de processo"
|
|
223
|
+
raise InvalidPageException(msg)
|
|
224
|
+
self.page.anexar_processo(processo)
|
|
225
|
+
|
|
226
|
+
def conceder_vistas(
|
|
227
|
+
self,
|
|
228
|
+
email: str,
|
|
229
|
+
tipo: Literal["acompanhamento", "disponibilizacao"],
|
|
230
|
+
motivo: str,
|
|
231
|
+
dias: int,
|
|
232
|
+
token: str,
|
|
233
|
+
) -> None:
|
|
234
|
+
if not isinstance(self.page, ProcessoPage):
|
|
235
|
+
msg = "página atual não é a página de processo"
|
|
236
|
+
raise InvalidPageException(msg)
|
|
237
|
+
if not isinstance(email, str):
|
|
238
|
+
msg = "email deve ser uma string"
|
|
239
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
240
|
+
if not email:
|
|
241
|
+
msg = "email deve ser uma string não vazia"
|
|
242
|
+
raise ValueError(msg)
|
|
243
|
+
if not isinstance(motivo, str):
|
|
244
|
+
msg = "motivo deve ser uma string"
|
|
245
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
246
|
+
if not motivo:
|
|
247
|
+
msg = "motivo deve ser uma string não vazia"
|
|
248
|
+
raise ValueError(msg)
|
|
249
|
+
if not isinstance(dias, int):
|
|
250
|
+
msg = "dias deve ser um int"
|
|
251
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
252
|
+
if not dias:
|
|
253
|
+
msg = "dias deve ser maior que zero"
|
|
254
|
+
raise ValueError(msg)
|
|
255
|
+
if not isinstance(token, str):
|
|
256
|
+
msg = "token deve ser uma string"
|
|
257
|
+
raise TypeError(msg) # pyright: ignore[reportUnreachable]
|
|
258
|
+
if not token:
|
|
259
|
+
msg = "token deve ser uma string não vazia"
|
|
260
|
+
raise ValueError(msg)
|
|
261
|
+
|
|
262
|
+
self.page.conceder_vistas(email, tipo, motivo, str(dias), token)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import final
|
|
2
|
+
|
|
3
|
+
from selenium.webdriver.common.by import By
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@final
|
|
7
|
+
class Locators:
|
|
8
|
+
# Login
|
|
9
|
+
InputUsuario = By.ID, "txtUsuario"
|
|
10
|
+
InputSenha = By.ID, "pwdSenha"
|
|
11
|
+
SelOrgao = By.ID, "selOrgao"
|
|
12
|
+
BtnLogin = By.ID, "sbmAcessar"
|
|
13
|
+
|
|
14
|
+
# Geral
|
|
15
|
+
FecharModalNovidade = By.XPATH, "//*[@title='Fechar janela (ESC)']"
|
|
16
|
+
CxUnidade = By.CSS_SELECTOR, "#divInfraBarraSistemaPadrao #lnkInfraUnidade"
|
|
17
|
+
LinhaUnidade = By.XPATH, "//td[contains(text(), '{}')]"
|
|
18
|
+
BuscaRapida = By.ID, "txtPesquisaRapida"
|
|
19
|
+
ProcessoNaoEncontrado = By.CSS_SELECTOR, "#conteudo > div"
|
|
20
|
+
|
|
21
|
+
# Controle de processos
|
|
22
|
+
TblRecebidos = By.ID, "tblProcessosRecebidos"
|
|
23
|
+
SelPagRecebidos = By.ID, "selRecebidosPaginacaoSuperior"
|
|
24
|
+
ProxPagRecebidos = By.ID, "lnkRecebidosProximaPaginaSuperior"
|
|
25
|
+
TblGerados = By.ID, "tblProcessosGerados"
|
|
26
|
+
SelPagGerados = By.ID, "selGeradosPaginacaoSuperior"
|
|
27
|
+
ProxPagGerados = By.ID, "lnkGeradosProximaPaginaSuperior"
|
|
28
|
+
|
|
29
|
+
# Processo
|
|
30
|
+
IframeArvore = By.ID, "ifrArvore"
|
|
31
|
+
IframeConteudo = By.ID, "ifrConteudoVisualizacao"
|
|
32
|
+
IframeVisualizacao = By.ID, "ifrVisualizacao"
|
|
33
|
+
DivStatus = By.ID, "divArvoreInformacao"
|
|
34
|
+
UnidadesStatus = By.TAG_NAME, "a"
|
|
35
|
+
Marcadores = By.XPATH, "//*[contains(@title, 'Marcador')]"
|
|
36
|
+
Prazo = By.XPATH, '//*[contains(@title, "Controle de Prazo:")]'
|
|
37
|
+
BtnAndamentos = By.CSS_SELECTOR, "#divConsultarAndamento > a"
|
|
38
|
+
TipoHistorico = By.ID, "ancTipoHistorico"
|
|
39
|
+
TblAndamentos = By.ID, "tblHistorico"
|
|
40
|
+
SelPagAndamentos = By.ID, "selInfraPaginacaoSuperior"
|
|
41
|
+
ProxPagAndamentos = By.ID, "lnkInfraProximaPaginaSuperior"
|
|
42
|
+
DivArvore = By.ID, "divArvore"
|
|
43
|
+
Documentos = By.XPATH, "//*/a[@target='ifrConteudoVisualizacao']"
|
|
44
|
+
Documento = By.LINK_TEXT, "{}"
|
|
45
|
+
DivArvoreInfo = By.ID, "divArvoreInformacao"
|
|
46
|
+
# Ações
|
|
47
|
+
AcaoMover = By.XPATH, "//*[@title='Mover Documento para outro Processo']"
|
|
48
|
+
AcaoConcluir = By.XPATH, "//*[@title='Concluir Processo']"
|
|
49
|
+
AcaoAnexar = By.XPATH, "//*[@title='Anexar Processo']"
|
|
50
|
+
AcaoVistas = (
|
|
51
|
+
By.XPATH,
|
|
52
|
+
"//*[@title='Gerenciar Disponibilizações de Acesso Externo']",
|
|
53
|
+
)
|
|
54
|
+
AcaoMarcadores = By.XPATH, "//*[@title='Gerenciar Marcador']"
|
|
55
|
+
# Mover Documento
|
|
56
|
+
ProcessoDestino = By.ID, "txtProcedimentoDestino"
|
|
57
|
+
BtnPesquisar = By.ID, "btnPesquisar"
|
|
58
|
+
InputMotivoMover = By.ID, "txaMotivo"
|
|
59
|
+
BtnMover = By.ID, "sbmMover"
|
|
60
|
+
# Concluir Processo
|
|
61
|
+
BtnSalvarConclusao = By.ID, "sbmSalvar"
|
|
62
|
+
# Anexar Processo
|
|
63
|
+
InputAnexar = By.ID, "txtProtocolo"
|
|
64
|
+
BtnPesquisar = By.ID, "btnPesquisar"
|
|
65
|
+
BtnAnexar = By.ID, "sbmAnexar"
|
|
66
|
+
# Vistas
|
|
67
|
+
InputEmailUnidade = By.ID, "selEmailUnidade"
|
|
68
|
+
FiltroUsuarioExterno = By.ID, "lblUsuariosExternos"
|
|
69
|
+
InputDestinatario = By.ID, "txtDestinatario"
|
|
70
|
+
AutocompleteDestinatario = By.ID, "divInfraAjaxtxtDestinatario"
|
|
71
|
+
InputEmailDestinatario = By.ID, "txtEmailDestinatario"
|
|
72
|
+
InputMotivoVistas = By.ID, "txaMotivo"
|
|
73
|
+
OptVistasIntegral = By.ID, "lblIntegral"
|
|
74
|
+
OptVistasParcial = By.ID, "lblParcial"
|
|
75
|
+
PermitirInclusaoDocs = By.ID, "lblSinInclusao"
|
|
76
|
+
SelecionarDocs = By.ID, "imgLupaSeries"
|
|
77
|
+
SelecionarProtocolos = By.ID, "imgLupaProtocolos"
|
|
78
|
+
IframeModal = By.NAME, "modal-frame"
|
|
79
|
+
FecharModalDocs = By.XPATH, "//*[@title='Fechar janela (ESC)']"
|
|
80
|
+
SelPagDocs = By.ID, "selInfraPaginacaoSuperior"
|
|
81
|
+
BtnTransportar = (
|
|
82
|
+
By.CSS_SELECTOR,
|
|
83
|
+
"#divInfraBarraComandosSuperior > #btnTransportarSelecao",
|
|
84
|
+
)
|
|
85
|
+
OptDocAnexo = (
|
|
86
|
+
By.CSS_SELECTOR,
|
|
87
|
+
"a#lnkInfraID-1917 ~ div.infraCheckboxDiv > label",
|
|
88
|
+
)
|
|
89
|
+
OptDocManifestacaoTecnica = (
|
|
90
|
+
By.CSS_SELECTOR,
|
|
91
|
+
"a#lnkInfraID-64 ~ div.infraCheckboxDiv > label",
|
|
92
|
+
)
|
|
93
|
+
OptTodosDocs = By.ID, "imgInfraCheck"
|
|
94
|
+
InputDiasVistas = By.ID, "txtDias"
|
|
95
|
+
InputTokenVistas = By.ID, "pwdSenha"
|
|
96
|
+
BtnDisponibilizar = By.ID, "btnDisponibilizar"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from selenium.common.exceptions import TimeoutException
|
|
2
|
+
from selenium.webdriver import Chrome
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.pages.components.navbar import Navbar
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasePage:
|
|
10
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
11
|
+
self.driver: Chrome = driver
|
|
12
|
+
self.wait: WebDriverWait[Chrome] = wait
|
|
13
|
+
|
|
14
|
+
def get_texto_alerta(self, timeout=5) -> str:
|
|
15
|
+
try:
|
|
16
|
+
return (
|
|
17
|
+
WebDriverWait(self.driver, timeout)
|
|
18
|
+
.until(EC.alert_is_present())
|
|
19
|
+
.text
|
|
20
|
+
)
|
|
21
|
+
except TimeoutException:
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
def confirmar_alerta(self):
|
|
25
|
+
self.driver.switch_to.alert.accept()
|
|
26
|
+
|
|
27
|
+
def fechar_alerta(self):
|
|
28
|
+
self.driver.switch_to.alert.dismiss()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BaseLoggedInPage(BasePage):
|
|
32
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
33
|
+
super().__init__(driver, wait)
|
|
34
|
+
self.navbar: Navbar = Navbar(driver, wait)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from selenium.webdriver import Chrome
|
|
2
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseComponent:
|
|
6
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
7
|
+
self.driver: Chrome = driver
|
|
8
|
+
self.wait: WebDriverWait[Chrome] = wait
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from selenium.common.exceptions import TimeoutException
|
|
2
|
+
from selenium.webdriver import Keys
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.locators import Locators as lc
|
|
7
|
+
from artespy.selenium.pages.components.base import BaseComponent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Navbar(BaseComponent):
|
|
11
|
+
def get_unidade_atual(self) -> str:
|
|
12
|
+
return self.wait.until(EC.element_to_be_clickable(lc.CxUnidade)).text
|
|
13
|
+
|
|
14
|
+
def acessar_pag_unidade(self) -> None:
|
|
15
|
+
self.wait.until(EC.element_to_be_clickable(lc.CxUnidade)).click()
|
|
16
|
+
|
|
17
|
+
def trocar_unidade(self, unidade: str) -> None:
|
|
18
|
+
u = self.get_unidade_atual()
|
|
19
|
+
if u.lower() == unidade.strip().lower():
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
self.acessar_pag_unidade()
|
|
23
|
+
|
|
24
|
+
loc = lc.LinhaUnidade[0], lc.LinhaUnidade[1].format(unidade)
|
|
25
|
+
self.wait.until(EC.element_to_be_clickable(loc)).click()
|
|
26
|
+
|
|
27
|
+
def buscar_processo(self, processo: str) -> bool:
|
|
28
|
+
self.driver.switch_to.default_content()
|
|
29
|
+
busca = self.wait.until(EC.element_to_be_clickable(lc.BuscaRapida))
|
|
30
|
+
busca.send_keys(processo)
|
|
31
|
+
busca.send_keys(Keys.ENTER)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
_ = WebDriverWait(self.driver, 5).until(
|
|
35
|
+
EC.visibility_of_element_located(lc.ProcessoNaoEncontrado)
|
|
36
|
+
)
|
|
37
|
+
except TimeoutException: # processo encontrado
|
|
38
|
+
return True
|
|
39
|
+
else:
|
|
40
|
+
return False
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from selenium.common.exceptions import TimeoutException
|
|
3
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
4
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
5
|
+
|
|
6
|
+
from artespy.selenium.locators import Locators as lc
|
|
7
|
+
from artespy.selenium.pages.base import BaseLoggedInPage
|
|
8
|
+
from artespy.selenium.pages.mixins.table import TableMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HomePage(BaseLoggedInPage, TableMixin):
|
|
12
|
+
def fechar_modal_novidades(self) -> None:
|
|
13
|
+
try:
|
|
14
|
+
WebDriverWait(self.driver, 5).until(
|
|
15
|
+
EC.element_to_be_clickable(lc.FecharModalNovidade)
|
|
16
|
+
).click()
|
|
17
|
+
except TimeoutException:
|
|
18
|
+
pass # modal ausente
|
|
19
|
+
|
|
20
|
+
def ler_tabela_recebidos(self) -> pd.DataFrame:
|
|
21
|
+
return self.ler_tabela(
|
|
22
|
+
lc.TblRecebidos, lc.SelPagRecebidos, lc.ProxPagRecebidos
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def ler_tabela_gerados(self) -> pd.DataFrame:
|
|
26
|
+
return self.ler_tabela(
|
|
27
|
+
lc.TblGerados, lc.SelPagGerados, lc.ProxPagGerados
|
|
28
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
2
|
+
from selenium.webdriver.support.ui import Select
|
|
3
|
+
from artespy.errors import InvalidCredentialsError, UnknownAlertException
|
|
4
|
+
from artespy.selenium.locators import Locators as lc
|
|
5
|
+
from artespy.selenium.pages.base import BasePage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoginPage(BasePage):
|
|
9
|
+
URL: str = "https://sei.sp.gov.br/sip/login.php?sigla_orgao_sistema=GESP&sigla_sistema=SEI&infra_url=L3NlaS8="
|
|
10
|
+
|
|
11
|
+
def acessar(self) -> None:
|
|
12
|
+
self.driver.get(self.URL)
|
|
13
|
+
|
|
14
|
+
def preencher_usuario(self, usuario: str) -> None:
|
|
15
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputUsuario)).send_keys(
|
|
16
|
+
usuario
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def preencher_senha(self, senha: str) -> None:
|
|
20
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputSenha)).send_keys(
|
|
21
|
+
senha
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def selecionar_orgao(self, orgao: str) -> None:
|
|
25
|
+
Select(
|
|
26
|
+
self.wait.until(EC.element_to_be_clickable(lc.SelOrgao))
|
|
27
|
+
).select_by_visible_text(orgao)
|
|
28
|
+
|
|
29
|
+
def clicar_login(self) -> None:
|
|
30
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnLogin)).click()
|
|
31
|
+
|
|
32
|
+
def fazer_login(self, usuario: str, senha: str, orgao: str) -> None:
|
|
33
|
+
self.acessar()
|
|
34
|
+
|
|
35
|
+
self.preencher_usuario(usuario)
|
|
36
|
+
self.preencher_senha(senha)
|
|
37
|
+
self.selecionar_orgao(orgao)
|
|
38
|
+
self.clicar_login()
|
|
39
|
+
|
|
40
|
+
t = self.get_texto_alerta()
|
|
41
|
+
if t:
|
|
42
|
+
if t.lower() == "usuário ou senha inválida.":
|
|
43
|
+
msg = "usuário, senha ou órgão inválidos"
|
|
44
|
+
raise InvalidCredentialsError(msg)
|
|
45
|
+
else:
|
|
46
|
+
msg = f"alerta com mensagem inesperada: '{t}'"
|
|
47
|
+
raise UnknownAlertException(msg)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from math import ceil
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
|
7
|
+
from selenium.webdriver.common.by import By
|
|
8
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
9
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
10
|
+
from selenium.webdriver.support.ui import Select, WebDriverWait
|
|
11
|
+
|
|
12
|
+
from artespy.errors import ElementNotFoundException
|
|
13
|
+
from artespy.selenium.locators import Locators as lc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TableMixin:
|
|
17
|
+
def get_tabela(self, locator: tuple[str, str]) -> WebElement | None:
|
|
18
|
+
sw = WebDriverWait(self.driver, 5)
|
|
19
|
+
try:
|
|
20
|
+
return sw.until(EC.visibility_of_element_located(locator))
|
|
21
|
+
except TimeoutException:
|
|
22
|
+
# Tabela de processos recebidos/gerados pode não estar presente
|
|
23
|
+
# caso nenhum processo da categoria correspondente esteja na
|
|
24
|
+
# unidade
|
|
25
|
+
if locator == lc.TblRecebidos or locator == lc.TblGerados:
|
|
26
|
+
return None
|
|
27
|
+
msg = f"Tabela '{locator}' não encontrada"
|
|
28
|
+
raise ElementNotFoundException(msg)
|
|
29
|
+
|
|
30
|
+
def calcular_num_pags(self, tbl: WebElement) -> int:
|
|
31
|
+
try:
|
|
32
|
+
caption = tbl.find_element(By.TAG_NAME, "caption")
|
|
33
|
+
except NoSuchElementException:
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
txt = caption.text
|
|
37
|
+
if not txt:
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
match = re.search(r"\d+", txt)
|
|
41
|
+
qtd_processos = int(match.group(0)) if match else 0
|
|
42
|
+
return ceil(qtd_processos / 100)
|
|
43
|
+
|
|
44
|
+
def ler_tabela(
|
|
45
|
+
self,
|
|
46
|
+
tbl_lc: tuple[str, str],
|
|
47
|
+
sel_lc: tuple[str, str],
|
|
48
|
+
btn_lc: tuple[str, str],
|
|
49
|
+
) -> pd.DataFrame:
|
|
50
|
+
tbl = self.get_tabela(tbl_lc)
|
|
51
|
+
if tbl is None:
|
|
52
|
+
return pd.DataFrame()
|
|
53
|
+
|
|
54
|
+
primeira_pag = pd.read_html(StringIO(tbl.get_attribute("outerHTML")))
|
|
55
|
+
df = primeira_pag[0]
|
|
56
|
+
|
|
57
|
+
pags = self.calcular_num_pags(tbl)
|
|
58
|
+
sw = WebDriverWait(self.driver, 10)
|
|
59
|
+
if pags >= 3:
|
|
60
|
+
sel = Select(sw.until(EC.visibility_of_element_located(sel_lc)))
|
|
61
|
+
for i in range(1, len(sel.options)):
|
|
62
|
+
# Relocalizar o select pra evitar StaleElementReferenceException
|
|
63
|
+
Select(
|
|
64
|
+
sw.until(EC.element_to_be_clickable(sel_lc))
|
|
65
|
+
).select_by_index(i)
|
|
66
|
+
pag = self.wait.until(EC.visibility_of_element_located(tbl_lc))
|
|
67
|
+
pag_df = pd.read_html(StringIO(pag.get_attribute("outerHTML")))
|
|
68
|
+
df = pd.concat([df, pag_df[0]], ignore_index=True)
|
|
69
|
+
if pags == 2:
|
|
70
|
+
sw.until(EC.element_to_be_clickable(btn_lc)).click()
|
|
71
|
+
|
|
72
|
+
pag = self.wait.until(EC.visibility_of_element_located(tbl_lc))
|
|
73
|
+
pag_df = pd.read_html(StringIO(pag.get_attribute("outerHTML")))
|
|
74
|
+
df = pd.concat([df, pag_df[0]], ignore_index=True)
|
|
75
|
+
if not pags:
|
|
76
|
+
try:
|
|
77
|
+
sel = Select(sw.until(EC.visibility_of_element_located(sel_lc)))
|
|
78
|
+
except TimeoutException:
|
|
79
|
+
pass # tabela não possui 3+ páginas
|
|
80
|
+
else:
|
|
81
|
+
for i in range(1, len(sel.options)):
|
|
82
|
+
# Relocalizar o select pra evitar StaleElementReferenceException
|
|
83
|
+
Select(
|
|
84
|
+
sw.until(EC.element_to_be_clickable(sel_lc))
|
|
85
|
+
).select_by_index(i)
|
|
86
|
+
pag = self.wait.until(
|
|
87
|
+
EC.visibility_of_element_located(tbl_lc)
|
|
88
|
+
)
|
|
89
|
+
pag_df = pd.read_html(
|
|
90
|
+
StringIO(pag.get_attribute("outerHTML"))
|
|
91
|
+
)
|
|
92
|
+
df = pd.concat([df, pag_df[0]], ignore_index=True)
|
|
93
|
+
|
|
94
|
+
return df
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
sw.until(EC.element_to_be_clickable(btn_lc)).click()
|
|
98
|
+
except TimeoutException:
|
|
99
|
+
pass # tabela não possui 2 páginas
|
|
100
|
+
else:
|
|
101
|
+
pag = self.wait.until(EC.visibility_of_element_located(tbl_lc))
|
|
102
|
+
pag_df = pd.read_html(StringIO(pag.get_attribute("outerHTML")))
|
|
103
|
+
df = pd.concat([df, pag_df[0]], ignore_index=True)
|
|
104
|
+
|
|
105
|
+
return df
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
import re
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from selenium.common.exceptions import TimeoutException
|
|
8
|
+
from selenium.webdriver import Chrome, Keys
|
|
9
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
10
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
11
|
+
from selenium.webdriver.support.ui import Select, WebDriverWait
|
|
12
|
+
|
|
13
|
+
from artespy.errors import (
|
|
14
|
+
ElementNotFoundException,
|
|
15
|
+
HandlerException,
|
|
16
|
+
SeiException,
|
|
17
|
+
UnknownAlertException,
|
|
18
|
+
)
|
|
19
|
+
from artespy.selenium.locators import Locators as lc
|
|
20
|
+
from artespy.selenium.pages.base import BaseLoggedInPage
|
|
21
|
+
from artespy.selenium.pages.mixins.table import TableMixin
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProcessoPage(BaseLoggedInPage, TableMixin):
|
|
27
|
+
def __init__(self, driver: Chrome, wait: WebDriverWait[Chrome]) -> None:
|
|
28
|
+
super().__init__(driver, wait)
|
|
29
|
+
self.sw: WebDriverWait[Chrome] = WebDriverWait(driver, 5)
|
|
30
|
+
|
|
31
|
+
def acessar_iframe_arvore(self) -> None:
|
|
32
|
+
self.driver.switch_to.default_content()
|
|
33
|
+
_ = self.wait.until(
|
|
34
|
+
EC.frame_to_be_available_and_switch_to_it(lc.IframeArvore)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def acessar_iframe_conteudo(self) -> None:
|
|
38
|
+
self.driver.switch_to.default_content()
|
|
39
|
+
_ = self.wait.until(
|
|
40
|
+
EC.frame_to_be_available_and_switch_to_it(lc.IframeConteudo)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def acessar_iframe_visualizacao(self) -> None:
|
|
44
|
+
self.acessar_iframe_conteudo()
|
|
45
|
+
_ = self.wait.until(
|
|
46
|
+
EC.frame_to_be_available_and_switch_to_it(lc.IframeVisualizacao)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def acessar_iframe_modal(self) -> None:
|
|
50
|
+
self.driver.switch_to.default_content()
|
|
51
|
+
_ = self.wait.until(
|
|
52
|
+
EC.frame_to_be_available_and_switch_to_it(lc.IframeModal)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def acessar_acao(self, loc: tuple[str, str]) -> None:
|
|
56
|
+
self.acessar_iframe_conteudo()
|
|
57
|
+
try:
|
|
58
|
+
self.wait.until(EC.element_to_be_clickable(loc)).click()
|
|
59
|
+
except TimeoutException:
|
|
60
|
+
msg = "timeout ao localizar ação"
|
|
61
|
+
raise ElementNotFoundException(msg)
|
|
62
|
+
|
|
63
|
+
def acessar_acao_mover(self) -> None:
|
|
64
|
+
self.acessar_acao(lc.AcaoMover)
|
|
65
|
+
|
|
66
|
+
def acessar_acao_concluir(self) -> None:
|
|
67
|
+
self.acessar_acao(lc.AcaoConcluir)
|
|
68
|
+
|
|
69
|
+
def acessar_acao_anexar(self) -> None:
|
|
70
|
+
self.acessar_acao(lc.AcaoAnexar)
|
|
71
|
+
|
|
72
|
+
def acessar_acao_vistas(self) -> None:
|
|
73
|
+
self.acessar_acao(lc.AcaoVistas)
|
|
74
|
+
|
|
75
|
+
def ler_status(self) -> str | list[str]:
|
|
76
|
+
self.acessar_iframe_visualizacao()
|
|
77
|
+
|
|
78
|
+
div_status = self.wait.until(
|
|
79
|
+
EC.visibility_of_element_located(lc.DivStatus)
|
|
80
|
+
)
|
|
81
|
+
txt = div_status.text
|
|
82
|
+
|
|
83
|
+
if not txt:
|
|
84
|
+
return ""
|
|
85
|
+
|
|
86
|
+
status: str | list[str] = ""
|
|
87
|
+
if txt.startswith("Processo aberto somente"):
|
|
88
|
+
u = div_status.find_element(
|
|
89
|
+
lc.UnidadesStatus[0], lc.UnidadesStatus[1]
|
|
90
|
+
)
|
|
91
|
+
status = u.text
|
|
92
|
+
elif txt.startswith("Processo aberto nas"):
|
|
93
|
+
elementos = div_status.find_elements(
|
|
94
|
+
lc.UnidadesStatus[0], lc.UnidadesStatus[1]
|
|
95
|
+
)
|
|
96
|
+
unidades = []
|
|
97
|
+
for e in elementos:
|
|
98
|
+
t = e.text
|
|
99
|
+
match = re.match(r"^\d*$|^\w*\.\w*$", t)
|
|
100
|
+
if match is not None:
|
|
101
|
+
continue
|
|
102
|
+
unidades.append(t)
|
|
103
|
+
status = unidades
|
|
104
|
+
elif txt.startswith("Processo sobrestado"):
|
|
105
|
+
status = "Sobretado"
|
|
106
|
+
elif txt.startswith("Processo anexado"):
|
|
107
|
+
status = "Incorporado"
|
|
108
|
+
elif txt.startswith("Processo não possui"):
|
|
109
|
+
status = "Concluído"
|
|
110
|
+
else:
|
|
111
|
+
msg = "div status começa com um fragmento de texto não registrado"
|
|
112
|
+
raise HandlerException(msg)
|
|
113
|
+
|
|
114
|
+
return status
|
|
115
|
+
|
|
116
|
+
def ler_marcadores(self) -> list[str]:
|
|
117
|
+
self.acessar_iframe_arvore()
|
|
118
|
+
|
|
119
|
+
ms = []
|
|
120
|
+
try:
|
|
121
|
+
elems = self.sw.until(
|
|
122
|
+
EC.visibility_of_all_elements_located(lc.Marcadores)
|
|
123
|
+
)
|
|
124
|
+
except TimeoutException:
|
|
125
|
+
return ms
|
|
126
|
+
for e in elems:
|
|
127
|
+
titulo = e.get_attribute("title")
|
|
128
|
+
if not titulo:
|
|
129
|
+
continue
|
|
130
|
+
m = titulo.split("\n")[1]
|
|
131
|
+
ms.append(m)
|
|
132
|
+
|
|
133
|
+
return ms
|
|
134
|
+
|
|
135
|
+
def ler_prazo(self) -> dt.date | None:
|
|
136
|
+
self.acessar_iframe_arvore()
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
e = self.sw.until(EC.visibility_of_element_located(lc.Prazo))
|
|
140
|
+
except TimeoutException:
|
|
141
|
+
return None
|
|
142
|
+
t = e.get_attribute("title")
|
|
143
|
+
if t is None:
|
|
144
|
+
return None
|
|
145
|
+
return dt.datetime.strptime(t.split(" ")[3], "%d/%m/%Y").date()
|
|
146
|
+
|
|
147
|
+
def visualizar_andamentos(self) -> None:
|
|
148
|
+
self.acessar_iframe_arvore()
|
|
149
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnAndamentos)).click()
|
|
150
|
+
self.acessar_iframe_visualizacao()
|
|
151
|
+
|
|
152
|
+
tipo = self.wait.until(EC.element_to_be_clickable(lc.TipoHistorico))
|
|
153
|
+
if tipo.text == "Ver histórico completo":
|
|
154
|
+
tipo.click()
|
|
155
|
+
|
|
156
|
+
def retornar_andamentos(self) -> pd.DataFrame:
|
|
157
|
+
self.acessar_iframe_visualizacao()
|
|
158
|
+
andamentos = self.ler_tabela(
|
|
159
|
+
lc.TblAndamentos, lc.SelPagAndamentos, lc.ProxPagAndamentos
|
|
160
|
+
)
|
|
161
|
+
andamentos["Data/Hora"] = pd.to_datetime(
|
|
162
|
+
andamentos["Data/Hora"], format="%d/%m/%Y %H:%M"
|
|
163
|
+
)
|
|
164
|
+
df = andamentos.drop_duplicates(ignore_index=True).sort_values(
|
|
165
|
+
by="Data/Hora", ascending=False
|
|
166
|
+
)
|
|
167
|
+
return df
|
|
168
|
+
|
|
169
|
+
def ler_andamentos(self) -> pd.DataFrame:
|
|
170
|
+
self.visualizar_andamentos()
|
|
171
|
+
return self.retornar_andamentos()
|
|
172
|
+
|
|
173
|
+
def get_documentos(self) -> list[WebElement]:
|
|
174
|
+
self.acessar_iframe_arvore()
|
|
175
|
+
return self.wait.until(
|
|
176
|
+
EC.visibility_of_all_elements_located(lc.Documentos)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def get_nomes_documentos(self) -> list[str]:
|
|
180
|
+
elems = self.get_documentos()
|
|
181
|
+
docs = []
|
|
182
|
+
for e in elems:
|
|
183
|
+
docs.append(e.text)
|
|
184
|
+
return docs
|
|
185
|
+
|
|
186
|
+
def acessar_documento(self, nome: str) -> None:
|
|
187
|
+
self.acessar_iframe_arvore()
|
|
188
|
+
loc = lc.Documento[0], lc.Documento[1].format(nome)
|
|
189
|
+
self.sw.until(EC.element_to_be_clickable(loc)).click()
|
|
190
|
+
|
|
191
|
+
def verificar_presenca_documento(self) -> bool:
|
|
192
|
+
self.acessar_iframe_visualizacao()
|
|
193
|
+
div_info = self.wait.until(
|
|
194
|
+
EC.visibility_of_element_located(lc.DivArvoreInfo)
|
|
195
|
+
)
|
|
196
|
+
if "Documento movido" in div_info.text:
|
|
197
|
+
return False
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def mover_documentos(self, nome: str, destino: str, motivo: str) -> None:
|
|
201
|
+
docs = self.get_nomes_documentos()
|
|
202
|
+
for d in docs:
|
|
203
|
+
if nome not in d:
|
|
204
|
+
continue
|
|
205
|
+
# Recibo não pode ser movido
|
|
206
|
+
if "Recibo Eletrônico" in d:
|
|
207
|
+
continue
|
|
208
|
+
self.acessar_documento(d)
|
|
209
|
+
presente = self.verificar_presenca_documento()
|
|
210
|
+
if not presente:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
self.acessar_acao_mover()
|
|
214
|
+
self.acessar_iframe_visualizacao()
|
|
215
|
+
|
|
216
|
+
self.wait.until(
|
|
217
|
+
EC.element_to_be_clickable(lc.ProcessoDestino)
|
|
218
|
+
).send_keys(destino)
|
|
219
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnPesquisar)).click()
|
|
220
|
+
|
|
221
|
+
t = self.get_texto_alerta()
|
|
222
|
+
if t:
|
|
223
|
+
if t == "Processo não encontrado.":
|
|
224
|
+
self.confirmar_alerta()
|
|
225
|
+
msg = f"processo destino ({destino}) não encontrado"
|
|
226
|
+
raise SeiException(msg)
|
|
227
|
+
else:
|
|
228
|
+
self.fechar_alerta()
|
|
229
|
+
msg = f"alerta com mensagem inesperada: '{t}'"
|
|
230
|
+
raise UnknownAlertException(msg)
|
|
231
|
+
self.wait.until(
|
|
232
|
+
EC.element_to_be_clickable(lc.InputMotivoMover)
|
|
233
|
+
).send_keys(motivo)
|
|
234
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnMover)).click()
|
|
235
|
+
logger.info(f"documento '{nome}' movido para {destino}")
|
|
236
|
+
|
|
237
|
+
def concluir_processo(self) -> None:
|
|
238
|
+
self.acessar_acao_concluir()
|
|
239
|
+
self.acessar_iframe_visualizacao()
|
|
240
|
+
self.wait.until(
|
|
241
|
+
EC.element_to_be_clickable(lc.BtnSalvarConclusao)
|
|
242
|
+
).click()
|
|
243
|
+
|
|
244
|
+
def anexar_processo(self, processo: str) -> None:
|
|
245
|
+
self.acessar_acao_anexar()
|
|
246
|
+
self.acessar_iframe_visualizacao()
|
|
247
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputAnexar)).send_keys(
|
|
248
|
+
processo
|
|
249
|
+
)
|
|
250
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnPesquisar)).click()
|
|
251
|
+
t = self.get_texto_alerta()
|
|
252
|
+
if t:
|
|
253
|
+
if t == "Processo não encontrado.":
|
|
254
|
+
self.confirmar_alerta()
|
|
255
|
+
msg = f"processo a ser anexado ({processo}) não encontrado"
|
|
256
|
+
raise SeiException(msg)
|
|
257
|
+
else:
|
|
258
|
+
self.fechar_alerta()
|
|
259
|
+
msg = f"alerta com mensagem inesperada: '{t}'"
|
|
260
|
+
raise UnknownAlertException(msg)
|
|
261
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnAnexar)).click()
|
|
262
|
+
|
|
263
|
+
def preencher_destinatario(self, email: str) -> None:
|
|
264
|
+
self.wait.until(
|
|
265
|
+
EC.element_to_be_clickable(lc.FiltroUsuarioExterno)
|
|
266
|
+
).click()
|
|
267
|
+
destinatario = self.wait.until(
|
|
268
|
+
EC.element_to_be_clickable(lc.InputDestinatario)
|
|
269
|
+
)
|
|
270
|
+
destinatario.send_keys(email)
|
|
271
|
+
try:
|
|
272
|
+
_ = self.wait.until(
|
|
273
|
+
EC.visibility_of_element_located(lc.AutocompleteDestinatario)
|
|
274
|
+
)
|
|
275
|
+
except TimeoutException:
|
|
276
|
+
self.wait.until(
|
|
277
|
+
EC.element_to_be_clickable(lc.InputEmailDestinatario)
|
|
278
|
+
).send_keys(email)
|
|
279
|
+
else:
|
|
280
|
+
destinatario.send_keys(Keys.ARROW_DOWN)
|
|
281
|
+
destinatario.send_keys(Keys.ENTER)
|
|
282
|
+
|
|
283
|
+
def preencher_motivo_vistas(self, motivo: str) -> None:
|
|
284
|
+
self.wait.until(
|
|
285
|
+
EC.element_to_be_clickable(lc.InputMotivoVistas)
|
|
286
|
+
).send_keys(motivo)
|
|
287
|
+
|
|
288
|
+
def preencher_opcoes_acompanhamento(self) -> None:
|
|
289
|
+
self.wait.until(
|
|
290
|
+
EC.element_to_be_clickable(lc.OptVistasIntegral)
|
|
291
|
+
).click()
|
|
292
|
+
self.wait.until(
|
|
293
|
+
EC.element_to_be_clickable(lc.PermitirInclusaoDocs)
|
|
294
|
+
).click()
|
|
295
|
+
self.wait.until(EC.element_to_be_clickable(lc.SelecionarDocs)).click()
|
|
296
|
+
|
|
297
|
+
self.acessar_iframe_modal()
|
|
298
|
+
|
|
299
|
+
self.wait.until(EC.element_to_be_clickable(lc.OptDocAnexo)).click()
|
|
300
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnTransportar)).click()
|
|
301
|
+
|
|
302
|
+
sel_pag = Select(
|
|
303
|
+
self.wait.until(EC.element_to_be_clickable(lc.SelPagDocs))
|
|
304
|
+
)
|
|
305
|
+
sel_pag.select_by_index(34)
|
|
306
|
+
self.wait.until(
|
|
307
|
+
EC.element_to_be_clickable(lc.OptDocManifestacaoTecnica)
|
|
308
|
+
).click()
|
|
309
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnTransportar)).click()
|
|
310
|
+
|
|
311
|
+
sel_pag = Select( # Evitar StaleElementReference
|
|
312
|
+
self.wait.until(EC.element_to_be_clickable(lc.SelPagDocs))
|
|
313
|
+
)
|
|
314
|
+
sel_pag.select_by_index(0)
|
|
315
|
+
|
|
316
|
+
self.driver.switch_to.default_content()
|
|
317
|
+
self.wait.until(EC.element_to_be_clickable(lc.FecharModalDocs)).click()
|
|
318
|
+
|
|
319
|
+
def preencher_opcoes_disponibilizacao(self) -> None:
|
|
320
|
+
self.wait.until(EC.element_to_be_clickable(lc.OptVistasParcial)).click()
|
|
321
|
+
self.wait.until(
|
|
322
|
+
EC.element_to_be_clickable(lc.SelecionarProtocolos)
|
|
323
|
+
).click()
|
|
324
|
+
|
|
325
|
+
self.acessar_iframe_modal()
|
|
326
|
+
|
|
327
|
+
self.wait.until(EC.element_to_be_clickable(lc.OptTodosDocs)).click()
|
|
328
|
+
self.wait.until(EC.element_to_be_clickable(lc.BtnTransportar)).click()
|
|
329
|
+
|
|
330
|
+
self.driver.switch_to.default_content()
|
|
331
|
+
self.wait.until(EC.element_to_be_clickable(lc.FecharModalDocs)).click()
|
|
332
|
+
|
|
333
|
+
def preencher_opcoes_cedoc(self) -> None:
|
|
334
|
+
self.wait.until(
|
|
335
|
+
EC.element_to_be_clickable(lc.OptVistasIntegral)
|
|
336
|
+
).click()
|
|
337
|
+
|
|
338
|
+
def conceder_vistas(
|
|
339
|
+
self,
|
|
340
|
+
email: str,
|
|
341
|
+
tipo: Literal["acompanhamento", "disponibilizacao"],
|
|
342
|
+
motivo: str,
|
|
343
|
+
dias: str,
|
|
344
|
+
token: str,
|
|
345
|
+
) -> None:
|
|
346
|
+
self.acessar_acao_vistas()
|
|
347
|
+
self.acessar_iframe_visualizacao()
|
|
348
|
+
|
|
349
|
+
Select(
|
|
350
|
+
self.wait.until(EC.element_to_be_clickable(lc.InputEmailUnidade))
|
|
351
|
+
).select_by_index(2)
|
|
352
|
+
|
|
353
|
+
self.preencher_destinatario(email)
|
|
354
|
+
self.preencher_motivo_vistas(motivo)
|
|
355
|
+
if tipo == "acompanhamento":
|
|
356
|
+
if (
|
|
357
|
+
"cedoc" in email
|
|
358
|
+
or "protocolo" in email
|
|
359
|
+
or "regulatorio" in email
|
|
360
|
+
or "adm.suref" in email
|
|
361
|
+
):
|
|
362
|
+
self.preencher_opcoes_cedoc()
|
|
363
|
+
else:
|
|
364
|
+
self.preencher_opcoes_acompanhamento()
|
|
365
|
+
elif tipo == "disponibilizacao":
|
|
366
|
+
self.preencher_opcoes_disponibilizacao()
|
|
367
|
+
|
|
368
|
+
self.acessar_iframe_visualizacao()
|
|
369
|
+
self.wait.until(
|
|
370
|
+
EC.element_to_be_clickable(lc.InputDiasVistas)
|
|
371
|
+
).send_keys(dias)
|
|
372
|
+
self.wait.until(
|
|
373
|
+
EC.element_to_be_clickable(lc.InputTokenVistas)
|
|
374
|
+
).send_keys(token)
|
|
375
|
+
self.wait.until(
|
|
376
|
+
EC.element_to_be_clickable(lc.BtnDisponibilizar)
|
|
377
|
+
).click()
|
|
378
|
+
logger.info(f"vistas concedidas para '{email}'")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: artespy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Leonardo Hossokawa de Oliveira <estagiario.seguros04@artesp.sp.gov.br>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: lxml>=6.0.2
|
|
8
|
+
Requires-Dist: pandas>=2.3.3
|
|
9
|
+
Requires-Dist: selenium>=4.38.0
|
|
10
|
+
Requires-Dist: webdriver-manager>=4.0.2
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
artespy/__init__.py,sha256=gYDuQkdNUX9hQy4rj1JHZZFa7ynX6ypUaR1bMPHR7es,87
|
|
2
|
+
artespy/errors/__init__.py,sha256=EjFOKtG9Z3Qdzo_LRYiJFzxCF-OIoTnEQvwFg0SyGfQ,444
|
|
3
|
+
artespy/selenium/handler.py,sha256=YdIGJq8ZNaPl9R2pD__-0RnTycma-3EPqLJjpQ5hxdw,10369
|
|
4
|
+
artespy/selenium/locators.py,sha256=zH9-qOexTcOfeE9eZtMP43uyduMU6vYidkD7a3Lt_9Q,3942
|
|
5
|
+
artespy/selenium/pages/base.py,sha256=HnFpwm0323O-zAYx2nOB425ERebhGOGmfXevEdhDp9E,1121
|
|
6
|
+
artespy/selenium/pages/home.py,sha256=Fjwsi-bCIbW2WL0V6jwqPm0V29h8Wg_E-IPBMJHKkBA,1027
|
|
7
|
+
artespy/selenium/pages/login.py,sha256=Kej4gxdp6PJhp-U5QNewcncsPDSl65lJLGd9KqEVsP0,1752
|
|
8
|
+
artespy/selenium/pages/processo.py,sha256=uNiNrXQERk0TpPfghoad1Tos6KH14_QMUMUIzZ8iVHc,13577
|
|
9
|
+
artespy/selenium/pages/components/base.py,sha256=pMUR4074h25hrhdveimvrrUo4NkYGHiKTocqy2ZEJ0k,287
|
|
10
|
+
artespy/selenium/pages/components/navbar.py,sha256=J1GL10wqdepZTkx4oVMuoqI1foqXZSVdhOsM45g7Pdw,1492
|
|
11
|
+
artespy/selenium/pages/mixins/table.py,sha256=me60-IRKeAcfzWf4Lw5FljuY5oXZt71UANCCcnCo1wk,4272
|
|
12
|
+
artespy-0.1.0.dist-info/METADATA,sha256=pzXODMRiVG69BRzigTThU711mgKpbRS1ZBOq1WOJ-7A,323
|
|
13
|
+
artespy-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
artespy-0.1.0.dist-info/entry_points.txt,sha256=7CEe8vB91bPbabrPYtw6P77JFVvwa-RkUKSWMWOlaiM,41
|
|
15
|
+
artespy-0.1.0.dist-info/RECORD,,
|