From 6826a8ee19247bf1cfd5a7ba8acbadf1fa7ffeb3 Mon Sep 17 00:00:00 2001 From: KirillGaliulin Date: Thu, 1 Aug 2024 14:50:18 +0500 Subject: [PATCH] feature: add new_form_creation_spec and creation_page --- lib/apress/selenium_products.rb | 5 +- .../product_creation_page/new_form_spec.rb | 635 ++++++++++++++++++ .../company_site/product_creation_page.rb | 340 ++++++++++ 3 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 lib/apress/selenium_products/spec/company_site/product_creation_page/new_form_spec.rb create mode 100644 lib/pages/company_site/product_creation_page.rb diff --git a/lib/apress/selenium_products.rb b/lib/apress/selenium_products.rb index 76ebc40..346542f 100644 --- a/lib/apress/selenium_products.rb +++ b/lib/apress/selenium_products.rb @@ -24,6 +24,9 @@ require "#{gem_directory}/lib/pages/company_site/eti/popups/traits_popup" require "#{gem_directory}/lib/pages/company_site/eti/popups/wholesale_price_popup" -# Мини-ЕТИ. +# Мини-ЕТИ require "#{gem_directory}/lib/pages/company_site/mini_eti/mini_eti" require "#{gem_directory}/lib/pages/company_site/mini_eti/pagination" + +# Новая форма создания товара +require "#{gem_directory}/lib/pages/company_site/product_creation_page" diff --git a/lib/apress/selenium_products/spec/company_site/product_creation_page/new_form_spec.rb b/lib/apress/selenium_products/spec/company_site/product_creation_page/new_form_spec.rb new file mode 100644 index 0000000..66e4267 --- /dev/null +++ b/lib/apress/selenium_products/spec/company_site/product_creation_page/new_form_spec.rb @@ -0,0 +1,635 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Новая форма создания товара', feature: 'Создание товара' do + before(:all) do + @product_page = ProductPage.new + @product_creation_page = CompanySite::ProductCreationPage.new + + log_in_as(:admin) + end + + after(:all) { log_out } + + context 'Когда переходим по урлу .../products/new' do + before do + navigate_to_product_creation_page + wait_until { @product_creation_page.page_title? } + end + + it 'выводится заголовок Создание товара или услуги' do + expect(@product_creation_page.page_title?).to be_truthy + end + end + + describe 'Когда заполняем поля и создаем товар' do + before(:all) { navigate_to_product_creation_page } + + context 'когда ни одно поле не заполнено' do + before { @product_creation_page.save } + + it 'отобразится ошибка сохранения под полем Название' do + expect(@product_creation_page.name_error?).to be_truthy + end + end + + context 'когда все поля заполнены максимальными значениями' do + before(:all) do + @options = { + name: Faker::Lorem.paragraph_by_chars(number: 120), + article: Faker::Lorem.paragraph_by_chars(number: 128), + exists: CONFIG['product_creation']['exists']['in_stock'], + qty_in_stock: 'Точное значение', + qty_exact: Faker::Number.number(digits: 10), + price: Faker::Number.number(digits: 10), + currency: CONFIG['product_creation']['currency']['usd'], + measure: CONFIG['product_creation']['measure']['box'], + wholesale_price: Faker::Number.number(digits: 10), + wholesale_qty: Faker::Number.number(digits: 6), + short_description: Faker::Lorem.paragraph_by_chars(number: 255), + description: CONFIG['product_creation']['full_description'], + tag_title: Faker::Lorem.characters(number: 255), + rubric: CONFIG['rubrics_names_by_level'][5], + priority_lvl4: true, + priority_lvl5: true, + company_traits: { + CONFIG['product_creation']['company_traits'][1] => CONFIG['product_creation']['company_trait_values'][1], + CONFIG['product_creation']['company_traits'][2] => CONFIG['product_creation']['company_trait_values'][2] + }, + portal_traits: { + CONFIG['product_creation']['portal_traits'][1] => CONFIG['product_creation']['portal_trait_values'][1], + CONFIG['product_creation']['portal_traits'][2] => CONFIG['product_creation']['portal_trait_values'][2] + }, + path_to_image: IMAGE_PATH, + qty_of_images: 4, # на самом деле будет загружено 10 изображений + description_of_photo: Faker::Lorem.paragraph_by_chars(number: 255), + group: CONFIG['product_creation']['group'], + path_to_doc: IMAGE_PATH, + qty_of_doc: 3, # на самом деле будет загружено 5 документов - это максимум + soputka: CONFIG['product_creation']['soputka'], + qty_of_soputka: 4 + } + + # На БЛ нет этого поля, только на ПЦ + if @product_creation_page.min_qty_input? + @options[:min_qty] = Faker::Number.number(digits: 10) + end + + @product_creation_page.fill_attributes(@options) + wait_until { @product_page.product_name? } + end + + it 'товар сохранился с заполненными полями. Открывается КТ' do + expect(Page.browser.current_url).to match Regexp.new( + CONFIG['eti_company_subdomain'] + '.' + CONFIG['portal_page_url'] + CONFIG['url_intermediate_kt'] + '/') + end + + it 'Название содержит 120 символов и совпадает с тем что вводили' do + expect(@product_page.product_name).to eq(@options[:name]) + expect(@product_page.product_name.size).to eq 120 + end + + it 'Розничная цена в usd/ящик совпадает с тем что вводили' do + expect(@product_page.product_price) + .to include "#{@options[:price].to_s.reverse.scan(/(\d{1,3})/).join(' ').reverse} usd/ящик" + end + + it 'Оптовая цена в usd/ящик совпадает с тем что вводили' do + expect(@product_page.product_wholesale_price).to include "от #{@options[:wholesale_qty]} ящик" + expect(@product_page.product_wholesale_price) + .to include "#{@options[:wholesale_price].to_s.reverse.scan(/(\d{1,3})/).join(' ').reverse} usd/ящик" + end + + it 'Наличие со статусом - В наличии' do + expect(@product_page.product_exists).to match(/[Вв] наличии/) + end + + it 'Артикул содержит 128 символов и совпадает с тем что вводили' do + expect(@product_page.product_article).to eq(@options[:article]) + expect(@product_page.product_article.size).to eq 128 + end + + it 'Изображения выводятся в количестве 10' do + expect(@product_page.product_images_elements.size).to eq 10 + end + + it 'Компанейские характеристики совпадают с тем что вводили' do + expect(@product_page.product_traits).to include 'Автотест комп хар-ка 1: max 30' + end + + it 'Полное описание совпадает с тем что вводили' do + expect(@product_page.product_description).to eq 'Текст для полного описания' + end + + it 'Документы выводятся в количестве 5' do + expect(@product_page.product_docs_elements.size).to eq 5 + end + + it 'товары Сопутки выводятся в количестве 4' do + expect(@product_page.product_soputka_elements.size).to eq 4 + end + end + end + + describe 'Когда проверяется одно из полей' do + before { navigate_to_product_creation_page } + + describe 'с отправкой формы' do + before { @product_creation_page.fill_attributes(options) } + + describe 'Цена розничная' do + context 'когда тип цены - Точная' do + let(:options) do + { + name: CONFIG['product_creation']['name']['valid'], + price: CONFIG['product_creation']['price']['exact'] + } + end + + it 'на КТ отобразится цена 500 руб.' do + expect(@product_page.product_price).to include '500' + end + end + + context 'когда тип цены - От-До' do + let(:options) do + { + name: CONFIG['product_creation']['name']['valid'], + price_from: CONFIG['product_creation']['price']['exact'], + price_to: CONFIG['product_creation']['price']['range_to'] + } + end + + # Проверка в 2 строчки, т.к. на ПЦ цена выводится в 1 строку, на БЛ в 2 строки + it 'на КТ отобразится цена от 500 до 800 руб.' do + expect(@product_page.product_price).to include 'от 500' + expect(@product_page.product_price).to include 'до 800' + end + end + + context 'когда тип цены - Со скидкой и без срока действия (10 лет)' do + let(:options) do + { + name: CONFIG['product_creation']['name']['valid'], + price_old: CONFIG['product_creation']['price']['exact'], + price_new: CONFIG['product_creation']['price']['after_discount'] + } + end + + # Проверка в 2 строчки, т.к. на ПЦ цена выводится в 1 строку, на БЛ в 2 строки + it 'на КТ отобразится цена со скидкой и старая цена' do + expect(@product_page.product_price).to include '300' + expect(@product_page.product_price).to include '500' + end + end + end + end + + describe 'до отправки формы' do + describe 'Рубрика' do + let(:options) { {rubric: CONFIG['rubrics_names_by_level'][5]} } + + context 'когда привязывается через выбор в дереве рубрик' do + before do + @product_creation_page.rubric_level1 + @product_creation_page.rubric_level2 + @product_creation_page.rubric_level3 + @product_creation_page.rubric_level4 + @product_creation_page.rubric_level5 + end + + it 'отобразится превью привязки к рубрике' do + expect(@product_creation_page.check_rubric).to be_truthy + end + end + + context 'когда привязывается через поиск' do + before { @product_creation_page.fill_rubric(options) } + + it 'отобразится превью привязки к рубрике' do + expect(@product_creation_page.check_rubric).to be_truthy + end + end + + context 'когда перевыбираем рубрику' do + before do + @product_creation_page.fill_rubric(options) + options[:rubric] = CONFIG['rubric_2'] # переустанока в переменную другой рубрики + @product_creation_page.fill_rubric(options) + end + + it 'отобразится вторая рубрика' do + expect(@product_creation_page.check_rubric).to include(options[:rubric]) + end + end + end + + describe 'Краткое описание' do + context 'когда введено < 50 символов' do + let(:options) { {short_description: CONFIG['product_creation']['short_description']['less_than_50']} } + + before { @product_creation_page.short_description = options[:short_description] } + + it 'под полем КО выводится предупреждение, что товар будет без КО, т.к. < 50 символов' do + expect(@product_creation_page.short_description_error).to include '- менее 50 символов' + end + end + + context 'когда текст из КО совпадает с Названием' do + let(:options) do + { + name: CONFIG['product_creation']['name']['valid'], + short_description: CONFIG['product_creation']['short_description']['as_the_name'] + } + end + + before do + @product_creation_page.name_input = options[:name] + @product_creation_page.short_description = options[:short_description] + end + + it 'под полем КО выводится предупреждение, что товар будет без КО, т.к. слова из названия' do + expect(@product_creation_page.short_description_error).to include '- слова из названия' + end + end + + context 'когда введено > 50 символов и не совпадает с названием' do + let(:options) do + { + name: CONFIG['product_creation']['name']['valid'], + short_description: CONFIG['product_creation']['short_description']['valid'] + } + end + + before do + @product_creation_page.name_input = options[:name] + @product_creation_page.short_description = options[:short_description] + end + + it 'под полем КО нет предупреждения' do + expect(@product_creation_page.short_description_error?).to be false + end + end + end + + # TODO: расскипать (см. ниже) + describe 'Фото', :skip => 'Вернуть проверки после применения нового образа на ноже для БЛ' do + context 'когда загружается изображение более 10 Мб' do + let(:options) { {path_to_image: Constants::MORE_10MB_IMAGE} } + + before { @product_creation_page.load_image(options) } + + it 'появится предупреждение о max размере 10 Мб' do + expect(@product_creation_page.image_error) + .to include 'Размер файла не должен превышать 10 Мб' + end + end + + context 'когда загружается изображение недопустимого формата' do + let(:options) { {path_to_image: Constants::NOT_IMAGE_PATH} } + + before { @product_creation_page.load_image(options) } + + it 'появится предупреждение о некорректном формате' do + expect(@product_creation_page.image_error) + .to include 'Файл должен быть корректным изображением jpg, png, gif, webp' + end + end + end + end + end + + # Одинаковые поля в БЛ и ПЦ заполняют батарейку на разные проценты + describe 'Когда проверяется батарейка' do + before { navigate_to_product_creation_page } + + context 'когда выполняется клик по "Все параметры" в плашке с батарейкой' do + before { @product_creation_page.thermometer_degree_popup_link } + + it 'откроется попап со справочной информацией о рассчете батарейки' do + expect(@product_creation_page.thermometer_degree_popup?).to be_truthy + end + end + + context 'когда заполнена Цена' do + let(:options) { {price: CONFIG['product_creation']['price']['exact']} } + + before { @product_creation_page.fill_price(options) } + + it "значение батарейки #{CONFIG['battery_percents']['price']}%" do + expect(@product_creation_page.thermometer_degree).to eq "#{CONFIG['battery_percents']['price']}%" + end + end + + context 'когда загружено Изображение' do + let(:options) { {path_to_image: IMAGE_PATH} } + + before { @product_creation_page.load_image(options) } + + it "значение батарейки #{CONFIG['battery_percents']['image']}%" do + expect(@product_creation_page.thermometer_degree).to eq "#{CONFIG['battery_percents']['image']}%" + end + end + + context 'когда заполнено Краткое описание' do + let(:options) { {short_description: CONFIG['product_creation']['short_description']['valid']} } + + before { @product_creation_page.short_description = options[:short_description] } + + it "значение батарейки #{CONFIG['battery_percents']['short_description']}%" do + expect(@product_creation_page.thermometer_degree).to eq "#{CONFIG['battery_percents']['short_description']}%" + end + end + + context 'когда заполнено Полное описание' do + let(:options) { {description: CONFIG['product_creation']['full_description']} } + + before { @product_creation_page.description = options[:description] } + + it "значение батарейки #{CONFIG['battery_percents']['description']}%" do + expect(@product_creation_page.thermometer_degree).to eq "#{CONFIG['battery_percents']['description']}%" + end + end + + context 'когда заполнено Наличие' do + let(:options) { {exists: CONFIG['product_creation']['exists']['in_stock']} } + + before { @product_creation_page.exists_select = options[:exists] } + + it "значение батарейки #{CONFIG['battery_percents']['exists']}%" do + expect(@product_creation_page.thermometer_degree).to eq "#{CONFIG['battery_percents']['exists']}%" + end + end + + context 'когда заполнены все основные поля' do + let(:options) do + { + price: CONFIG['product_creation']['price']['exact'], + path_to_image: IMAGE_PATH, + short_description: CONFIG['product_creation']['short_description']['valid'], + description: CONFIG['product_creation']['full_description'], + exists: CONFIG['product_creation']['exists']['in_stock'] + } + end + + before do + @product_creation_page.fill_price(options) + @product_creation_page.load_image(options) + @product_creation_page.short_description = options[:short_description] + @product_creation_page.description = options[:description] + @product_creation_page.exists_select = options[:exists] + end + + it 'значение батарейки 100%' do + expect(@product_creation_page.thermometer_degree).to eq '100%' + end + end + end + + describe 'Редактирование товара' do + context 'когда изменяем название и цену' do + before(:all) do + @options = { + name: CONFIG['product_creation']['name']['valid'], + price: CONFIG['product_creation']['price']['exact'] + } + + navigate_to_product_creation_page + @product_creation_page.fill_attributes(@options) + @product_page.product_edit_link + @product_creation_page.clear_string(@options) + @options[:name] = 'Новое название товара' + @options[:price] = '200' + @product_creation_page.fill_attributes(@options) + end + + it 'на карточке товара отобразится новое название' do + expect(@product_page.product_name).to eq 'Новое название товара' + end + + it 'на карточке товара отобразится новая цена' do + expect(@product_page.product_price).to include '200' + end + end + end + + describe 'Модерация товара' do + context 'когда товар одобренный' do + before(:all) do + @options = {name: 'Автотест, модерация: одобрен'} + navigate_to_product_creation_page + @product_creation_page.fill_attributes(@options) + @product_page.product_edit_link + end + + after do + @product_creation_page.accept + reload_page + end + + context 'когда отклоняем товар по выбранной причине' do + before do + @product_creation_page.decline + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации выводится сообщение об отклонении' do + expect(@product_creation_page.moderation_message) + .to include CONFIG['product_creation']['moderation']['message']['declined'] + expect(@product_creation_page.moderation_message).to include 'Доработать контенту' + end + end + + context 'когда отклоняем товар по кастомной причине' do + before do + @product_creation_page.decline + @product_creation_page.cause_input = 'Кастомная причина отклонения' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации выводится сообщение об отклонении' do + expect(@product_creation_page.moderation_message) + .to include CONFIG['product_creation']['moderation']['message']['declined'] + expect(@product_creation_page.moderation_message).to include 'Кастомная причина отклонения' + end + end + + context 'когда отправляем на рассмотрение товар по выбранной причине' do + before do + @product_creation_page.postpone + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации выводится сообщение о рассмотрении' do + expect(@product_creation_page.moderation_message) + .to include CONFIG['product_creation']['moderation']['message']['postponed'] + expect(@product_creation_page.moderation_message).to include 'Доработать контенту' + end + end + end + + context 'когда товар отклоненный' do + before(:all) do + @options = {name: 'Автотест, модерация: отклонен'} + navigate_to_product_creation_page + @product_creation_page.fill_attributes(@options) + @product_page.product_edit_link + @product_creation_page.decline + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + context 'когда одобряем товар' do + before do + @product_creation_page.accept + reload_page + end + + after do + @product_creation_page.decline + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации не выводится сообщение об отклонении' do + expect(@product_creation_page.moderation_message?).to be false + end + end + + context 'когда отправляем на рассмотрение товар по кастомной причине' do + before do + @product_creation_page.postpone + @product_creation_page.cause_input = 'Кастомная причина для отправки на рассмотрение' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации выводится сообщение о рассмотрении' do + expect(@product_creation_page.moderation_message) + .to include CONFIG['product_creation']['moderation']['message']['postponed'] + expect(@product_creation_page.moderation_message).to include 'Кастомная причина для отправки на рассмотрение' + end + end + end + + context 'когда товар на рассмотрении' do + before(:all) do + @options = {name: 'Автотест, модерация: на рассмотрении'} + navigate_to_product_creation_page + @product_creation_page.fill_attributes(@options) + @product_page.product_edit_link + @product_creation_page.postpone + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + context 'когда одобряем товар' do + before do + @product_creation_page.accept + reload_page + end + + after do + @product_creation_page.postpone + @product_creation_page.cause_select = 'Доработать контенту' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации не выводится сообщение о рассмотрении' do + expect(@product_creation_page.moderation_message?).to be false + end + end + + context 'когда отклоняем товар по кастомной причине' do + before do + @product_creation_page.decline + @product_creation_page.cause_input = 'Кастомная причина для отклонения' + @product_creation_page.moderation_submit + @product_creation_page.wait_moderation_message + end + + it 'над блоком основной информации выводится сообщение о рассмотрении' do + expect(@product_creation_page.moderation_message) + .to include CONFIG['product_creation']['moderation']['message']['declined'] + expect(@product_creation_page.moderation_message).to include 'Кастомная причина для отклонения' + end + end + end + end + + describe 'Удаление товара' do + before(:all) do + @options = {name: 'Автотест, редактирование'} + navigate_to_product_creation_page + @product_creation_page.fill_attributes(@options) + end + + context 'когда выходим из редактирования и сохраняем изменения' do + before do + @product_page.product_edit_link + @product_creation_page.clear_string(@options) + @options = {name: 'Автотест, выход с сохранением'} + @product_creation_page.name_input = @options[:name] + @product_creation_page.quit + @product_creation_page.edit_yes + end + + it 'на карточке товара отобразится новое название' do + expect(@product_page.product_name).to eq 'Автотест, выход с сохранением' + end + end + + context 'когда выходим из редактирования без сохранения изменений' do + before do + @product_page.product_edit_link + @product_creation_page.clear_string(@options) + @options = {name: 'Автотест, выход без сохранения'} + @product_creation_page.name_input = @options[:name] + @product_creation_page.quit + @product_creation_page.edit_no + end + + it 'на карточке товара отобразится старое название' do + expect(@product_page.product_name).to eq 'Автотест, выход с сохранением' + end + end + + context 'когда нажимаем Удалить товар и оставляем товар' do + before(:all) do + @product_page.product_edit_link + @product_creation_page.delete + @product_creation_page.edit_no + end + + it 'попап закрывается' do + expect(@product_creation_page.edit_popup?).to be false + end + + it 'остается страница редактирования товара' do + expect(@product_creation_page.page_title?).to be_truthy + end + end + + context 'когда нажимаем Удалить товар и подтверждаем выбор' do + before do + @product_creation_page.delete + @product_creation_page.edit_yes + @end_url = CONFIG['url_catalog_sk'] + end + + it 'открывается каталог СК' do + expect(Page.browser.current_url).to end_with @end_url + end + end + end +end diff --git a/lib/pages/company_site/product_creation_page.rb b/lib/pages/company_site/product_creation_page.rb new file mode 100644 index 0000000..136f499 --- /dev/null +++ b/lib/pages/company_site/product_creation_page.rb @@ -0,0 +1,340 @@ +# frozen_string_literal: true + +module CompanySite + class ProductCreationPage < Page + div(:page_title, css: '.company-admin-content-wrap .product') + + # Блок Основная информация (Название товара, Артикул, Наличие) + text_area(:name_input, css: '.aui-admin-text-input__field.name') + span(:name_error, css: '.aui-admin-text-invalid__text') + text_area(:article_input, css: '.main-info__content-wrapper .aui-admin-text-input__field') + select(:exists_select, css: '.main-info__content-wrapper .aui-admin-select__input') + select(:qty_in_stock_select, css: '.main-info__qty-in-stock select') + text_area(:qty_exact_input, css: '.main-info__qty-in-stock input') + + # Блок Цены + # Розничная + # Точная + text_area(:price_input, css: 'input[placeholder="Укажите размер цены"]') + # От-До + button(:price_from_to_button, xpath: '//*[@class="product__section price"]//label[2]') + text_area(:price_from_input, css: 'input[placeholder="Укажите минимальный размер цены"]') + text_area(:price_to_input, css: 'input[placeholder="Укажите максимальный размер цены"]') + # Скидка + button(:discount_price_button, xpath: '//*[@class="product__section price"]//label[3]') + text_area(:discount_price_old_input, css: 'input[placeholder="Укажите размер цены"]') + text_area(:discount_price_new_input, css: 'input[placeholder="Укажите размер цены со скидкой"]') + div(:activate_calendar_discount, css: '.calendar .price-date-block') + button(:discount_without_expiration, css: '.calendar__block .without-action-time') + # Валюта и Единицы измерения + select(:currency_select, css: 'div.product__content.product__content_long-bottom div.price__currency select') + select(:measure_select, css: 'div.product__content.product__content_long-bottom div.product__input select') + # Оптовая + # Цена + checkbox(:wholesale_checkbox, css: '[for="is-wholesale-price"]') + text_area(:wholesale_price_input, css: 'input[placeholder="Укажите размер оптовой цены"]') + text_area(:wholesale_qty_input, css: 'input[placeholder="Минимальный оптовый заказ"]') + # Валюта и Единицы измерения + select(:wholesale_currency_select, xpath: '//*[@class="product__content"]//*[@class="price__currency"]//select') + select(:wholesale_measure_select, xpath: '//*[@class="product__content"]//*[@class="product__input"]//select') + + # Блок Описание (КО, ПО, Тэг ) + text_area(:short_description, xpath: '//div[@class="short-description__textarea"]//textarea') + span(:short_description_error, css: '.aui-admin-text-invalid__text') + text_area(:product_description, css: 'textarea[name="product_editor"]') + div(:ckeditor, css: '#cke_product_description') + text_area(:tag_title_input, css: '.tag-title .aui-admin-text-input__field') + + # Блок Рубрика для публикации на портале (Выбор рубрики, Ранее выбранные рубрики) + text_area(:rubric_input, css: '.rubricator .aui-admin-search__input') + button(:rubric_find_button, css: '.rubricator .aui-admin-search__button') + link(:rubric_level1, xpath: "//*[text()='#{CONFIG['rubrics_names_by_level'][1]}']") + link(:rubric_level2, xpath: "//*[text()='#{CONFIG['rubrics_names_by_level'][2]}']") + link(:rubric_level3, xpath: "//*[text()='#{CONFIG['rubrics_names_by_level'][3]}']") + link(:rubric_level4, xpath: "//*[text()='#{CONFIG['rubrics_names_by_level'][4]}']") + link(:rubric_level5, xpath: "//*[text()='#{CONFIG['rubrics_names_by_level'][5]}']") + link(:finded_rubric, xpath: "(//div[@class='aui-admin-rubric-block__link'])[1]") + span(:check_rubric, css: '.aui-admin-rubric-block_active') + button(:change_rubric, css: '.aui-admin-edit-button') + span(:rubricator, css: '.rubric-select-tree-block') + + # Блок Приоритет в рубрике + checkbox(:priority_lvl4_checkbox, css: '[for="rubric_l4_priority"]') + checkbox(:priority_lvl5_checkbox, css: '[for="rubric_l5_priority"]') + + # Блок Характеристики товаров + # На вашем сайте + text_area(:user_trait_input, xpath: '(//div[@class="company-trait__item"]//input)[last()]') + button(:add_user_trait, css: '.company-trait__add .aui-admin-link') + text_area(:user_trait_value_input, xpath: '(//div[@class="company-trait-value__item"]//input)[last()]') + button(:add_user_trait_value, css: '.company-trait-value__buttons .aui-admin-link') + # На портале + button(:switch_to_portal_traits, xpath: '//label[contains(text(),"На портале")]') + elements(:portal_trait_name, xpath: '//div[@class="rubric-trait"]//label') + elements(:portal_trait_value, css: '.rubric-trait > input') + + # Блок Фото + button(:load_image_button, xpath: '//div[@class="image-loader__block"]/../..//input[@type="file"]') + image(:first_mini_image, css: 'img.aui-admin-image-preview__image') + image(:last_mini_image, xpath: '(//img[@class="aui-admin-image-preview__image"])[last()]') + text_area(:description_of_photo, css: '.image-block__textarea .aui-admin-textarea__field') + div(:image_error, css: 'div.image-loader__error-text') + + # Блок Группа товаров + elements(:groups_tree, css: '.groups__selector-block .aui-admin-rubric-select__title') + + # Блок Документы + button(:load_file, xpath: '//div[@class="document-loader__block"]/../..//input[@type="file"]') + elements(:docs, css: '.documents__reorder-block .document-block') + + # Блок Сопутствующие товары + text_area(:soputka_input, css: '.related-products__search .aui-admin-search__input') + button(:soputka_find_button, css: '.related-products__search .aui-admin-search__button') + button(:add_in_soputka_button, css: '.related-products__search-products .aui-admin-related-products__button') + + # Минимальный размер заказа (только для ПЦ) + text_area(:min_qty_input, css: '.min-quantity .aui-admin-number-input__field') + + # Кнопка сохранения + button(:save, css: '.aui-admin-button_submit') + + # Удаление и выход из редактирования + # Открывается попап удаления/выхода с одинаковыми классами у обеих кнопок, но разным текстом + button(:quit, css: '.product .page-info__edit-product-control-buttons_quit') + button(:delete, css: '.product .page-info__edit-product-control-buttons_delete') + span(:edit_popup, css: '.product .page-info-popup__block') + button(:edit_yes, css: '.page-info-popup__block [type="submit"]') + button(:edit_no, css: '.page-info-popup__block .page-info-popup__link') + + # Модерация + # Кнопки на форме + button(:accept, css: '.aui-admin-moderation-button_accept') + button(:decline, css: '.aui-admin-moderation-button_decline') + button(:postpone, css: '.aui-admin-moderation-button_postpone') + # Попап модерации + span(:moderation_popup, css: '.moderation-popup__block') + textarea(:cause_input, css: '.moderation-popup__block .moderation-popup__textarea') + select(:cause_select, css: '.moderation-popup__block .aui-admin-select__input') + button(:moderation_submit, css: '.moderation-popup__block .aui-admin-button') + # Уведомление + span(:moderation_message, css: 'div.page-info__notice') + + # Батарейка + span(:thermometer_degree, css: '.aui-admin-battery__percent') + link(:thermometer_degree_popup_link, css: '.aui-admin-battery__link') + span(:thermometer_degree_popup, css: '.aui-admin-popup') + + # ОПИСАНИЕ МЕТОДОВ + # + # Основной метод последовательного заполнения атрибутов, переданых в переменной options из спека + # Если атрибут есть в переменной options, то заполняется; если атрибута нет, то пропускается + def fill_attributes(options) + fill_main_block(options) + fill_price(options) + fill_description_block(options) + fill_rubric_block(options) + fill_traits_block(options) + + load_image(options) if options[:path_to_image] + fill_group(options) if options[:group] + load_doc(options) if options[:path_to_doc] + fill_soputka(options) if options[:soputka] + self.min_qty_input = options[:min_qty] if options[:min_qty] + + save + end + + # Заполнение блока Основная информация + def fill_main_block(options) + self.name_input = options[:name] if options[:name] + self.article_input = options[:article] if options[:article] + self.exists_select = options[:exists] if options[:exists] + self.qty_in_stock_select = options[:qty_in_stock] if options[:qty_in_stock] + self.qty_exact_input = options[:qty_exact] if options[:qty_exact] + end + + # Заполнение блока Цены + def fill_price(options) + fill_price_exact(options) if options[:price] + fill_price_range(options) if options[:price_from] + fill_discount_price(options) if options[:price_old] + fill_wholesale_price(options) if options[:wholesale_price] + end + + # Заполнение блока Описание + def fill_description_block(options) + self.short_description = options[:short_description] if options[:short_description] + self.description = options[:description] if options[:description] + end + + # Заполнение блока Рубрика и ее приоритет + def fill_rubric_block(options) + fill_rubric(options) if options[:rubric] + fill_priority_in_rubric(options) if options[:priority_lvl4 || :priority_lvl5] + end + + # Заполнение блока Характеристики товара + def fill_traits_block(options) + fill_company_traits(options) if options[:company_traits] + fill_portal_traits(options) if options[:portal_traits] + end + + # Заполнение розничной цены точной + def fill_price_exact(options) + self.price_input = options[:price] + self.currency_select = options[:currency] if options[:currency] + self.measure_select = options[:measure] if options[:measure] + end + + # Заполнение розничной цены от-до + def fill_price_range(options) + price_from_to_button + self.price_from_input = options[:price_from] + self.price_to_input = options[:price_to] + self.currency_select = options[:currency] if options[:currency] + self.measure_select = options[:measure] if options[:measure] + end + + # Заполнение розничной цены со скидкой с max датой окончания скидки (10 лет) + def fill_discount_price(options) + discount_price_button + self.discount_price_old_input = options[:price_old] + self.discount_price_new_input = options[:price_new] + activate_calendar_discount_element.click + discount_without_expiration + self.currency_select = options[:currency] if options[:currency] + self.measure_select = options[:measure] if options[:measure] + end + + # Заполнение оптовой цены + def fill_wholesale_price(options) + check_wholesale_checkbox + self.wholesale_price_input = options[:wholesale_price] + self.wholesale_qty_input = options[:wholesale_qty] + self.wholesale_currency_select = options[:currency] if options[:currency] + self.wholesale_measure_select = options[:measure] if options[:measure] + end + + # Вставка текста в полное описание + def description=(text) + wait_until { ckeditor? } + type_to_ck_editor('product_description', text) # метод из модуля apress-selenium_integration + end + + # Поиск и выбор рубрики + def fill_rubric(options) + change_rubric_element.click unless change_rubric_not_exists? # редактирование рубрики, если есть кнопка + self.rubric_input = options[:rubric] + rubric_find_button + finded_rubric + end + + # Заполнение приоритета у рубрики + def fill_priority_in_rubric(options) + options[:priority_lvl4] == true ? check_priority_lvl4_checkbox : uncheck_priority_lvl4_checkbox + options[:priority_lvl5] == true ? check_priority_lvl5_checkbox : uncheck_priority_lvl5_checkbox + end + + # Заполнение компанейских характеристик + def fill_company_traits(options) + options[:company_traits].each do |trait, value| + self.user_trait_input = trait + self.user_trait_value_input = value + add_user_trait + end + end + + # Заполнение портальных характеристик + def fill_portal_traits(options) + switch_to_portal_traits + options[:portal_traits].each { |trait, value| fill_portal_traits_values(trait, value) } + end + + # Заполнение значений у портальных характеристик + def fill_portal_traits_values(trait, value) + browser + .action + .click(portal_trait_value_elements[trait_index(trait)].element) + .perform + + Page.elements(:available_trait_values, :link, css: '.select-options > div') + available_trait_values_elements.find { |trait_value| trait_value.text == value }.click + end + + def trait_index(trait) + portal_trait_name_elements.index { |element| element.text == trait } + end + + # Загрузка заданных изображений + def load_image(options) + if options[:qty_of_images] + options[:qty_of_images].times do + upload_file(load_image_button_element, options[:path_to_image]) + end + else + upload_file(load_image_button_element, options[:path_to_image]) + end + + last_mini_image_element.when_visible(30) if options[:qty_of_images] + self.description_of_photo = options[:description_of_photo] if options[:description_of_photo] + end + + # Выбор группы + def fill_group(options) + name_group = options[:group] + groups_tree_elements.find { |group| group.text.strip == name_group }.click + end + + # Загрузка заданных документов + def load_doc(options) + options[:qty_of_doc].times do + upload_file(load_file_element, options[:path_to_doc]) + end + + wait_until { docs_elements.size == 5 } + end + + # Выбор сопутствующих товаров + def fill_soputka(options) + self.soputka_input = options[:soputka] + soputka_find_button + options[:qty_of_soputka].times do + add_in_soputka_button + sleep 1 + end + end + + # Очистка ТОЛЬКО текстовых строк + # Название строк которые нужно очистить передаются хэшом в параметр + # Например, в спеке объявляется переменная + # @options = { + # name: CONFIG['product_creation']['name']['valid'], + # price: CONFIG['product_creation']['price']['exact'] + # } + # Этот метод очистит текстовые строки name и price + def clear_string(names_strings) + elements = names_strings.keys.map(&:to_s) + + elements.each do |element_name| + el = send("#{element_name}_input_element") + + browser + .action + .move_to(el.element) + .click + .perform + + clear_field + end + end + + # Метод для перезагрузки страницы и ожидания отрисовки сообщения модерации + # Очень редко может зафейлить. При быстрых действиях может отрисоваться некорректный статус + # на странице и неверные кнопки модерации, поэтому при некорректном сообщении перезагружаем страницу + def wait_moderation_message + reload_page + wait_until { moderation_message? } + reload_page if moderation_message == 'Внимание! Показ на портале остановлен' + end + end +end