Русский
Русский
English
Статистика
Реклама

Tutorials

Из песочницы Как использовать Axios в React

03.10.2020 16:08:05 | Автор: admin


В этой статье вы узнаете как использовать библиотеку Axios в React.

Рассмотрим все на примере запросов на сервер, отобразим полученные данные в таблице, сдалаем компонент проверки загрузки и все это используя React Hooks.

Что такое Axios?


Axios это один из самых популярных HTTP клиентов для браузеров и node.js, основанный на промисах.

В Axios есть поддержка запросов, получение ответов от сервера, их трансформация и автоматическая конвертация в JSON.

Перед тем как начать, создадим новый React проект:

npx create-react-app react-axios-table

Зайдем в него:

cd react-axios-table

Данные для проекта


В качестве данных для нашего проекта будем использовать массив объектов:

[{id: 101,firstName: 'Sue',lastName: 'Corson',email: 'DWhalley@in.gov',phone: '(612)211-6296',address: {streetAddress: '9792 Mattis Ct',city: 'Waukesha',state: 'WI',zip: '22178'},description: 'et lacus magna dolor...',}]

Ссылка на данные

Настройка компонента для работы с Axios


Загружаем библиотеку Axios:

npm i axios

Импортируем axios в компонент из которого будем отправлять запросы на сервер:

import axios from 'axios'

Т.к. в проекте мы используем React Hooks, импортируем useState и useEffect (подробнее про хуки в реакте читаем по ссылке):

import React, { useEffect, useState } from 'react';

Далее в компонент добавляем следующий код:

function App() {    const [appState, setAppState] = useState();    useEffect(() => {    const apiUrl = 'http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}';    axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState(allPersons);    });  }, [setAppState]);   return (    <div className="app">        </div>  );}export default App;

Рассмотрим код поближе:

const [appState, setAppState] = useState();

Отвечает за состояние стейта в компоненте:

 useEffect(() => {}, [setAppState])

useEffect будет следить за изменением setAppState и производить ререндер если это необходимо

 const apiUrl=''

сюда подставляем нашу ссылку

axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState(allPersons);    });

отправляем get запрос на сервер, затем полученные JSON данные сохраняем в стейт

Компонент проверки загрузки


В src создадим папку components. В ней создаем компонент UserData.js и добавляем следующий код:
function UserData(props) {    const { persons } = props    if (!persons || persons.length === 0) return <p>Нет данных.</p>    return (        <div>            <table>                <thead>                    <tr>                        <th>id</th>                        <th>firstName</th>                        <th>lastName</th>                        <th>email</th>                        <th>phone</th>                    </tr>                </thead>                <tbody>                    {                        persons.map((person) =>                            <tr key={person.id}>                                <td>{person.id}</td>                                <td>{person.firstName}</td>                                <td>{person.lastName}</td>                                <td>{person.email}</td>                                <td>{person.phone}</td>                            </tr>                        )                    }                </tbody>            </table>      </div>    )}export default UserData

В пропсы компонента мы будем передавать данные полученные с сервера

 if (!persons || persons.length === 0) return <p>Нет данных.</p>

делаем проверку, есть ли данные с сервера

{                        persons.map((person) =>                            <tr key={person.id}>                                <td>{person.id}</td>                                <td>{person.firstName}</td>                                <td>{person.lastName}</td>                                <td>{person.email}</td>                                <td>{person.phone}</td>                            </tr>                        )                    }

Методом map проходим по массиву с людьми, для каждого человека создаем строку. Не забываем про key.

В папке components создаем LoadingPersonsData.js и вставляем следующий код:

function OnLoadingUserData(Component) {    return function LoadingPersonsData({ isLoading, ...props }) {        if (!isLoading) return <Component {...props} />        else return (            <div>                <h1>Подождите, данные загружаются!</h1>            </div>        )    }}export default LoadingPersonsData

Код выше это higher-order component в React. В качестве пропсов он принимает другой компонент и затем возвращает какую-либо логику. В нашем случае компонент проверяет isLoading. Пока данные загружаются, мы отображаем сообщение о загрузке, как только isLoading станет false мы возвращаем компонент с данными.

Внесем изменения в наш App.js для возможности проверки загрузки данных.

Импортируем наши компоненты в App.js:

import UserData from './components/UserData'import OnLoadingUserData from './components/OnLoadingUserData'

Добавляем следующий код:

function App() {  const DataLoading =  LoadingPersonsData(UserData);  const [appState, setAppState] = useState(    {      loading: false,      persons: null,    }  ) useEffect(() => {    setAppState({loading: true})    const apiUrl = 'http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}';    axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState({      loading: false,      persons: allPersons       });    });  }, [setAppState]);  return (    <div className="app">        <DataLoading isLoading={appState.loading} persons={appState.persons} />    </div>  );}export default App;

 const DataLoading =  LoadingPersonsData(UserData);

Мы создаем новый компонент, приравниваем его к нашему higher-order компоненту и обворачиваем им UserData (компонент отображения данных).

В стейт мы добавляем новое свойство loading: false, по нему мы будем определять загрузку данных с сервера.

<DataLoading isLoading={appState.loading} persons={appState.persons} />

Рендерим компонент и передаем пропсы в наш higher-order компонент.

Добавим немного css и при загрузки данных увидим следующее окно:

image

А теперь, когда get запрос на сервер завершен успешно, данные получены:

image

Теперь мы знаем как использовать Axios get с REST API.

Если у вас есть вопросы или пожелания, оставляйте их в комментариях. Буду рад ответить.
Подробнее..

Перевод Как читать файлы конфигурации в тестах с Selenium на Python

16.09.2020 18:22:11 | Автор: admin
Привет, хабр. В преддверии старта курса Python QA Engineer подготовили для вас еще один интересный перевод.



Руководство, описанное в этой статье, поможет вам в тестировании веб-интерфейсов. Мы создадим простое надежное решение для тестирования веб-интерфейса с помощью Python, pytest и Selenium WebDriver. Мы рассмотрим стратегии построения хороших тестов и паттерны написания правильных автоматизированных тестов. Конечно же, разработанный проект по тестированию сможет послужить хорошей основой для создания собственных тест-кейсов.

Какой браузер?


Тест с поиском в DuckDuckGo из одной из предыдущих глав работает просто отлично но только в Chrome. Взглянем на фикстуру browser еще разок:

@pytest.fixturedef browser():  driver = Chrome()  driver.implicitly_wait(10)  yield driver  driver.quit()


Тип драйвера и время ожидания заданы жестко. Для доказательства концепции это, может, и хорошо, но тесты на продакшене нужно уметь конфигурировать в рантайме. Тесты для веб-интерфейсов должны работать в любом браузере. Значения таймаута по умолчанию должны регулироваться на случай, если одни среды работают медленнее, чем другие. Конфиденциальные данные, такие как имена пользователей и пароли, также никогда не должны появляться в исходном коде. Как же работать с такими тестовыми данными?

Все эти значения являются данными конфигурации системы автоматизированного тестирования. Они представляют собой дискретные значения, которые системно влияют на то, как работает автоматизация. Данные конфигурации должны приходить на вход с каждым запуском тестов. Все, что связано с конфигурацией тестов и среды, должно рассматриваться как конфигурационные данные, чтобы код автоматизации можно было переиспользовать.

Источники входных данных


В системе автоматизированного тестирования есть несколько способов считывать входные данные:

  • Аргументы командной строки;
  • Переменные среды;
  • Свойства системы;
  • Файлы конфигурации;
  • Запросы к API.


К сожалению, большинство платформ для тестирования не поддерживают чтение данных из аргументов командной строки. Переменными среды и свойствами системы сложно управлять и их потенциально опасно обрабатывать. API сервисов это отличный способ потреблять входные данные, особенно получать секреты (например, пароли) от службы управления ключами, такой как, например, AWS KMS или Azure Key Vault. Однако платить за такой функционал может быть недопустимо, а писать самостоятельно неразумно. В таком случае лучшим вариантом будут конфигурационные файлы.

Config-файл это обычный файл, который содержит данные конфигурации. Автоматизированное тестирование может считывать его при запуске тестов и использовать входные значения для управления тестами. Например, в config-файле может быть указан тип браузера, который используется в качестве фикстуры browser в нашем примере проекта. Как правило, файлы конфигурации стандартного формата, например, JSON, YAML или INI. Также они должны быть плоскими, чтобы их можно было легко отличить от других файлов.

Наш config-файл


Давайте напишем файл конфигурации для нашего проекта по тестированию. Мы воспользуемся форматом JSON, поскольку он прост в использовании, популярен и в нем четко выделена иерархия. К тому же, модуль json это стандартная библиотека Python, которая с легкостью конвертирует файлы JSON в словари. Создайте новый файл с именем tests/config.json и добавьте следующий код:

{  "browser": "chrome",  "wait_time": 10}


JSON использует пары ключ-значение. Как мы уже говорили, в нашем проекте есть два значения конфигурации: выбор браузера и время ожидания. Здесь browser это строка, а wait_time целое число.

Чтение config-файла с pytest


Фикстуры это лучший способ читать файлы конфигурации с помощью pytest. С их помощью можно читать config-файлы перед началом тестов, а затем вставлять значения в тесты или даже другие фикстуры. Добавьте следующую фикстуру в tests/test_web.py:

import json@pytest.fixture(scope='session')def config():  with open('tests/config.json') as config_file:    data = json.load(config_file)  return data


Фикстура config читает и парсит файл tests/config.json в словарь с помощью модуля json. Жестко заданные пути к файлам довольно распространенная практика. На самом же деле многие инструменты и системы автоматизации будут проверять наличие файлов в нескольких директориях или по шаблонам именования. Область действия фикстуры установлена в session, поэтому фикстура запустится один раз за тестовую сессию. Нет необходимости читать один и тот же файл конфигурации каждый раз в новом тесте это неэффективно!

Входные данные конфигурации нужны при инициализации WebDriver. Обновите фикстуру browser следующим образом:

@pytest.fixturedef browser(config):  if config['browser'] == 'chrome':    driver = Chrome()  else:    raise Exception(f'"{config["browser"]}" is not a supported browser')  driver.implicitly_wait(config['wait_time'])  yield driver  driver.quit()


Фикстура browser теперь будет иметь зависимость от фикстуры config. Даже если config запустится один раз за тестовую сессию, browser все равно будет вызываться перед каждым тестом. Теперь у browser есть цепочка if-else, чтобы определить, какой тип WebDriver использовать. На данный момент поддерживается только Chrome, но скоро мы добавим еще несколько типов. Если браузер не определится, выпадет исключение. Неявное время ожидания также будет брать свое значение из файла конфигурации.

Поскольку browser все еще возвращает экземпляр WebDriver, тесты, которые его используют не нужно рефакторить! Давайте запустим тесты, чтобы удостовериться, что config-файл работает:

$ pipenv run python -m pytest tests/test_web.py ============================= test session starts ==============================platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testingcollected 1 item                                                               tests/test_web.py .                                                      [100%]=========================== 1 passed in 5.00 seconds ===========================


Добавляем новые браузеры


Теперь, когда у нашего проекта есть config-файл, его можно использовать, чтобы поменять браузер. Давайте запустим тест на Mozilla Firefox вместо Google Chrome. Для этого загрузите и установите последнюю версию Firefox, а затем загрузит последнюю версию geckodriver (драйвер для Firefox). Убедитесь, что geckodriver также есть в system path.

Обновите код фикстуры browser для работы с Firefox:

from selenium.webdriver import Chrome, Firefox@pytest.fixturedef browser(config):  if config['browser'] == 'chrome':    driver = Chrome()  elif config['browser'] == 'firefox':    driver = Firefox()  else:    raise Exception(f'"{config["browser"]}" is not a supported browser')  driver.implicitly_wait(config['wait_time'])  yield driver  driver.quit()


Затем добавьте в config-файл опцию firefox:

{  "browser": "firefox",  "wait_time": 10}


А теперь перезапустите тест, и увидите окно Firefox вместо Chrome!



Валидация


Несмотря на то, что config-файл работает, в логике его обработки есть существенный недостаток: данные не проверяются перед запуском тестов. Фикстура browser вызовет исключение, если браузер будет выбран некорректно, но произойдет это для каждого теста. Будет гораздо эффективнее, если исключение такого типа будет выпадать один раз за тестовую сессию. Помимо этого, тестирование упадет, если в config-файле будут отсутствовать ключи browser или wait_time. Давайте это исправим.

Добавьте новую фикстуру для валидации выбора браузера:

@pytest.fixture(scope='session')def config_browser(config):  if 'browser' not in config:    raise Exception('The config file does not contain "browser"')  elif config['browser'] not in ['chrome', 'firefox']:    raise Exception(f'"{config["browser"]}" is not a supported browser')  return config['browser']


Фикстура config_browser зависит от фикстуры config. Также, как и у config, у нее scope = session. Мы получим исключение, если в файле конфигурации не будет ключа browser или если выбранный браузер не поддерживается. Наконец, она возвращает выбранный браузер, чтобы тесты и другие фикстуры могли спокойно получить доступ к этому значению.

Дальше следующая фикстура для валидации времени ожидания:

@pytest.fixture(scope='session')def config_wait_time(config):  return config['wait_time'] if 'wait_time' in config else 10


Если в config-файле указано время ожидания, то фикстура config_wait_time вернет его. В противном случае, она вернет значение в 10 секунд по умолчанию.
Обновите фикстуру browser еще раз, чтобы использовать новые фикстуры для валидации:

@pytest.fixturedef browser(config_browser, config_wait_time):  if config_browser == 'chrome':    driver = Chrome()  elif config_browser == 'firefox':    driver = Firefox()  else:    raise Exception(f'"{config_browser}" is not a supported browser')  driver.implicitly_wait(config_wait_time)  yield driver  driver.quit()


Написание отдельных функций фикстур для каждого значения данных конфигурации делает их простыми, четкими и определенными. Также они позволяют объявлять только те значения, которые нужны для отправки запросов.

Запустите тест и убедитесь, что все работает:

$ pipenv run python -m pytest tests/test_web.py ============================= test session starts ==============================platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testingcollected 1 item                                                               tests/test_web.py .                                                      [100%]=========================== 1 passed in 4.58 seconds ===========================


И это круто! Однако, чтобы валидация прошла более реалистично, нужно быть хитрыми. Давайте поменяем значение browser на safari неподдерживаемый браузер.

$ pipenv run python -m pytest tests/test_web.py ============================= test session starts ==============================platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testingcollected 1 item                                                               tests/test_web.py E                                                      [100%]==================================== ERRORS ====================================________________ ERROR at setup of test_basic_duckduckgo_search ________________config = {'browser': 'safari', 'wait_time': 10}    @pytest.fixture(scope='session')    def config_browser(config):      # Validate and return the browser choice from the config data      if 'browser' not in config:        raise Exception('The config file does not contain "browser"')      elif config['browser'] not in SUPPORTED_BROWSERS:>       raise Exception(f'"{config["browser"]}" is not a supported browser')E       Exception: "safari" is not a supported browsertests/conftest.py:30: Exception=========================== 1 error in 0.09 seconds ============================


Вау! В ошибке было четко указано из-за чего она появилась. А теперь, что случится, если мы удалим выбор браузера из config-файла?

$ pipenv run python -m pytest tests/test_web.py ============================= test session starts ==============================platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testingcollected 1 item                                                               tests/test_web.py E                                                      [100%]==================================== ERRORS ====================================________________ ERROR at setup of test_basic_duckduckgo_search ________________config = {'wait_time': 10}    @pytest.fixture(scope='session')    def config_browser(config):      # Validate and return the browser choice from the config data      if 'browser' not in config:>       raise Exception('The config file does not contain "browser"')E       Exception: The config file does not contain "browser"tests/conftest.py:28: Exception=========================== 1 error in 0.10 seconds ============================


Отлично! Еще одно полезное сообщение об ошибке. Для последнего теста добавим выбор браузера, но уберем время ожидания:

$ pipenv run python -m pytest tests/test_web.py ============================= test session starts ==============================platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testingcollected 1 item                                                               tests/test_web.py .                                                      [100%]=========================== 1 passed in 4.64 seconds ===========================


Тест должен отработать, поскольку время ожидания опционально. Что ж, изменения, которые мы внесли, пошли на пользу! Помните, что иногда вам нужно тестировать еще и свои тесты.

Итоговый тест


Есть еще две небольшие вещи, которые мы можем сделать, чтобы сделать код теста чище. Во-первых, давайте переместим наши веб-фикстуры в файл conftest.py, чтобы ими могли пользоваться все тесты, а не только тесты в tests/test_web.py. Во-вторых, давайте вытащим несколько буквенных значений в переменные модуля.

Создайте новый файл с именем tests/conftest.py со следующим кодом:

import jsonimport pytestfrom selenium.webdriver import Chrome, FirefoxCONFIG_PATH = 'tests/config.json'DEFAULT_WAIT_TIME = 10SUPPORTED_BROWSERS = ['chrome', 'firefox']@pytest.fixture(scope='session')def config():  # Read the JSON config file and returns it as a parsed dict  with open(CONFIG_PATH) as config_file:    data = json.load(config_file)  return data@pytest.fixture(scope='session')def config_browser(config):  # Validate and return the browser choice from the config data  if 'browser' not in config:    raise Exception('The config file does not contain "browser"')  elif config['browser'] not in SUPPORTED_BROWSERS:    raise Exception(f'"{config["browser"]}" is not a supported browser')  return config['browser']@pytest.fixture(scope='session')def config_wait_time(config):  # Validate and return the wait time from the config data  return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME@pytest.fixturedef browser(config_browser, config_wait_time):  # Initialize WebDriver  if config_browser == 'chrome':    driver = Chrome()  elif config_browser == 'firefox':    driver = Firefox()  else:    raise Exception(f'"{config_browser}" is not a supported browser')  # Wait implicitly for elements to be ready before attempting interactions  driver.implicitly_wait(config_wait_time)    # Return the driver object at the end of setup  yield driver    # For cleanup, quit the driver  driver.quit()


Полное содержание tests/test_web.py теперь должно быть проще и чище:

import pytestfrom pages.result import DuckDuckGoResultPagefrom pages.search import DuckDuckGoSearchPagedef test_basic_duckduckgo_search(browser):  # Set up test case data  PHRASE = 'panda'  # Search for the phrase  search_page = DuckDuckGoSearchPage(browser)  search_page.load()  search_page.search(PHRASE)  # Verify that results appear  result_page = DuckDuckGoResultPage(browser)  assert result_page.link_div_count() > 0  assert result_page.phrase_result_count(PHRASE) > 0  assert result_page.search_input_value() == PHRASE


Ну вот, это уже в стиле Python!

Что дальше?


Итак, код примера нашего проекта по тестированию завершен. Вы можете использовать его в качестве базы для создания новых тестов. Финальный пример проекта вы также можете найти на GitHub. Однако то, что мы закончили писать код, не значит, что мы закончили обучение. В следующих статьях мы будем говорить о том, как вывести автоматизацию тестирования на Python на новый уровень!

Подробнее..

Перевод Автоматизация тестирования. Да или нет?

03.09.2020 18:19:12 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Python QA Engineer.





Прежде чем спрашивать: Что автоматизировать?, нужно ответить на такой вопрос: Рационально ли вообще автоматизировать что-либо в рамках текущего проекта?. Если ответ положительный (что означает, что у вас есть все необходимые ресурсы для обеспечения автоматизации, такие как квалифицированные QA-специалисты, достаточно времени, денег и т.д.) необходимо создать план, основываясь на требованиях тестируемого объекта, для которого и будут разрабатываться автоматизированные тесты. При создании такого документа должно быть четкое понимание того, что именно вы хотите автоматизировать, как и какие средства автоматизации выбрать. Сейчас мы не будем вдаваться в подробности того, как именно тестировать ту или иную функцию, поскольку нас интересует, где, по нашему мнению, должна быть реализована автоматизация.

Конечно, приведенный ниже список далеко не полный, да и детализированный список тестов для проверки каждой функции кажется бессмысленной затеей. Наша задача выбрать в проекте те части, которые нужно автоматизировать в первую очередь:

  1. Часто используемый функционал, где достаточно высок риск ошибок. Автоматизированное тестирование ключевых функциональных точек сократит время на поиск ошибок и, соответственно, время необходимое на их устранение, тоже сократится.
  2. Типичные часто выполняемые операции, связанные с обработкой данных. Например, формы, в которых количество полей, подлежащих заполнению, достаточно велико. Цель здесь в том, чтобы автоматизировать ввод данных в нужное поле и проверить правильность выполнения задачи после получения результата.
  3. Сообщения о валидации. Нужно автоматизировать отправку неверных данных соответствующим полям и протестировать корректность работы проверки данных и сообщения об ошибках.
  4. Комплексное тестирование поведения всей системы как целостного объекта (сквозное тестирование).
  5. Проверка данных, требующих точных математических расчетов (бухгалтерское или аналитическое программное обеспечение).
  6. Проверка корректности отображаемых результатов поиска в ответ на запрос по данным (проверка корректности поиска данных).
  7. Валидация поиска данных.


Что не автоматизировать?


Какие типы тестов не нужно включать в автоматизированное тестирование? Давайте перечислим ситуации, в которых тест-кейсы автоматизировать не нужно:

  1. Юзабилити-тесты, требующие ручного вмешательства для проверки на наличие ошибок или отклонение от ожидаемого поведения;
  2. Тест-кейсы, которые включают в себя установку или не требуют повторного выполнения функции (тем не менее, тесты, которые подразумевают ввод данных в том, автоматизировать необходимо);
  3. Избегайте автоматизации тестов, которые могут привести к непредсказуемым результатам (например, новые функции, временные тесты, проверка на истечение срока службы).
  4. UX-тесты, которые включают в себя проверку поведения объектов на экранах различных размеров.

Для повышения эффективности автоматизированного тестирования, особое внимание следует уделять тест-кейсам. Необходимо выделить те из них, которые ориентированы на тестирование следующих аспектов:

  1. Операции создания/чтения/обновления/удаления (CRUD-операции). Самый простой пример пользовательский интерфейс. Ввод, просмотр и редактирование данных пользователя, удаление информации.
  2. Стандартные сценарии использования приложения. Примером может служить работа с почтовым клиентом: авторизация, просмотр писем, навигация по полученным письмам, создание новых и их отправка, выход из системы. Эта сквозная последовательность проверяет полный спектр действий и манипуляций. Преимущество таких сценариев заключается в том, что в конце теста система возвращается в исходное состояние (ну или близко к нему), что означает, что влияние на результаты других тестов уменьшается.
  3. Другие кейсы, когда по каким-то причинам не подходит ручное тестирование. Например, проверка структуры файлов, созданных системой.

Именно от автоматизации такого функционала можно получить больше всего пользы!



Читать ещё:


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru