- Регистрация
- 28.07.2014
- Сообщения
- 825
- Благодарностей
- 653
- Баллы
- 93
Все мы сталкиваемся с багами, они были, есть и будут есть нам мозг, особенно если мы взаимодействуем со сторонними ресурсами, например сайтами.
А если баги еще и плавающие, да и не обязательно наши, например подглючивает сайт и выдает не то что должен, да еще и поймать этот момент в ProjectMaker не получается....
В версиях до 5.10.x.x отладичка никакого не было, можно было изголяться и делать отладку в CodeCreator, но запустить его одновременно с ProjectMaker нельзя, да и очень он тормозит в режиме отладки.
Начиная с версии 5.10.x.x в Zennoposter появился отладчик для сниппетов, но он тоже,мягко говоря неудобный, и очень тугодумный.
Но мы-то знаем, что есть такой замечательный продукт как VisualStudio, с отличными возможностями дебага, более удобным редактором кода, и кучей других дополнительных вкусных плюшек, значит надо как-то подружить студию с ZennoPoster.
Сразу возникает вопрос, что нам для этого надо ?
А надо нам иметь доступ к объектам project и instance.
Начинаем разбираться и видим, что instance наследник от MarshalByRefObject, лезем в доки зеннопостера, находим там возможность внешнего подключения к инстансу и тихонько радуемся такому везению ).
Отлично, первая часть решена, остался project.
C project,ом чуть сложнее, он не поддерживает маршалинг, и на вскидку приходит 2 решения, или сделать прокси объект поддерживающий маршалинг, или делать себе локальную копию используя интерфейс.
Я остановился на 2м варианте, т.к. с маршалингом не возился и времени возиться не было, да и во время написания и дебага в студии, мне обычно не надо делать изменения в проекте.
Задача ясна, цели поставленны, приступаем )
1. Создаем класс наследник от IZennoPosterProjectModel, и реализуем в нем все нужные нам методы.
Мне в 99% случаев нужен доступ к профилю, текущим, локальным переменным, глобальным переменным, и project.Context.
2. Создаем скрипты Сериализации и экспорта + Десереализации и импорта для проекта, т.к. лень было прописывать все свойства руками, делаем это через рефлексию.
3. В проекте в любом нужном нам месте делаем экспорт, в студии делаем импорт, и спокойно работаем с кодом.
Скрипт экспорта, добавляем в общий код:
И для самого экспорта создаем кубик с одним вызовом:
Собранные нами слепок, сохраняется во временную папку, в имени файла присутсвует порт инстанса, он нужен для возможности отладки инстансов в самом постере, но об этом чуть позже
Экспорт, сделали, дальше берем студию, и запускаем в ней отладочный проект (ZennoDebug), который содержит в себе метод импорта и нашу реализацию project.
Здесь у нас для разных версий ZP может возникнуть небольшая проблемка, т.к. IZennoPosterProjectModel от версии к версии немного меняется, и в одной версии он один в другой чуть другой, в приложеном проекте можно переключаться между ZP 5.9.9.1 и ZP 5.10.7.0
Для других версий внести правки, думаю у вас труда не составит, т.к. студия все что не так подсветит и при желании даже сама добавит.
В итоге в основной программе имеем такой код:
В метод TestSnippet, вставляем код нужного нам для отладки кубика, и спокойно дебажим.
А так как у нас есть подключение к инстансу, мы еще можем очень удобно работать в окне C# interactive, и тут-же видеть результат в браузере.
Я например 90% xPath запросов в нем и составляю, затем прогон кода через отладчик, и только потом вносим изменения в проект в ProjectMakere.
И на десерт, что делать если у нас плавающие проблемы, и отловить их в ProjectMaker, ну никак не получается.
Не проблема будем ловить их в ZennoPoster, и также подклюаться к нему как и к ProjectMaker.
Для этого в бэденд вставляем такой код:
Код проверяет, включен ли режим отладки для данного проекта (WaitForDebug), и если включен отображает нам браузер, шлет алерт куда нам надо (это уже сами доделаете) и дает нам определенное время подключиться к нему из студии (WaitForDebugTimeout).
Надо это для того, чтобы в многопотоке не получить кучу вечно ожидающих отладки инстансов.
При подключении к инстансу из студии нам достаточно добавить в заголовок инстанса слово "DEBUG", чтобы проект знал что им занимаются и закрываться не надо.
В алерте о необходимости отладки мы видим порт инстанса по которому нам надо подключиться, и ид кубика где произошла проблема.
В проекте студии меняем порт инстанса на нужный нам, и спокойно подключаемся для разбора проблемы.
На этом все.
P.S. Если надо использовать ZennoPoster класс, например распознавание капчи, запускайте студию под администратором, иначе будете ломать голову почему не работает.
P.P.S. Я не ошибся разделом, смысла кидать эту статью на конкурс я не вижу, да и все красиво расписывать откровенно лень, но т.к. ребята давно просили выложить, нашел в себе силы и немного времени расписать )
А если баги еще и плавающие, да и не обязательно наши, например подглючивает сайт и выдает не то что должен, да еще и поймать этот момент в ProjectMaker не получается....
В версиях до 5.10.x.x отладичка никакого не было, можно было изголяться и делать отладку в CodeCreator, но запустить его одновременно с ProjectMaker нельзя, да и очень он тормозит в режиме отладки.
Начиная с версии 5.10.x.x в Zennoposter появился отладчик для сниппетов, но он тоже,мягко говоря неудобный, и очень тугодумный.
Но мы-то знаем, что есть такой замечательный продукт как VisualStudio, с отличными возможностями дебага, более удобным редактором кода, и кучей других дополнительных вкусных плюшек, значит надо как-то подружить студию с ZennoPoster.
Сразу возникает вопрос, что нам для этого надо ?
А надо нам иметь доступ к объектам project и instance.
Начинаем разбираться и видим, что instance наследник от MarshalByRefObject, лезем в доки зеннопостера, находим там возможность внешнего подключения к инстансу и тихонько радуемся такому везению ).
Отлично, первая часть решена, остался project.
C project,ом чуть сложнее, он не поддерживает маршалинг, и на вскидку приходит 2 решения, или сделать прокси объект поддерживающий маршалинг, или делать себе локальную копию используя интерфейс.
Я остановился на 2м варианте, т.к. с маршалингом не возился и времени возиться не было, да и во время написания и дебага в студии, мне обычно не надо делать изменения в проекте.
Задача ясна, цели поставленны, приступаем )
1. Создаем класс наследник от IZennoPosterProjectModel, и реализуем в нем все нужные нам методы.
Мне в 99% случаев нужен доступ к профилю, текущим, локальным переменным, глобальным переменным, и project.Context.
2. Создаем скрипты Сериализации и экспорта + Десереализации и импорта для проекта, т.к. лень было прописывать все свойства руками, делаем это через рефлексию.
3. В проекте в любом нужном нам месте делаем экспорт, в студии делаем импорт, и спокойно работаем с кодом.
Скрипт экспорта, добавляем в общий код:
C#:
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using ZennoLab.CommandCenter;
using ZennoLab.InterfacesLibrary.ProjectModel;
using ZennoLab.InterfacesLibrary.ProjectModel.Collections;
namespace ZennoDebug
{
/// <summary>
/// Експорт данных из зенки в XML через datatable
/// </summary>
public static class Export
{
/// <summary>
/// Преобразуем свойства объекта в дататейбл для дальнейшей записи в xml, например project.Profile
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static DataTable ObjectPropertiesToDataTable(object obj, string tableName = "ObjectTable"/*,string [] excludeProperties = null*/)
{
DataTable table = new DataTable(tableName);
System.Data.DataRow valueRow = table.NewRow();
System.Data.DataRow typeRow = table.NewRow();
// Выбираем все названия свойств в список
var list = obj.GetType().GetProperties().Select(a => a.Name).ToList();
// Обходим все свойства, берем их значения и записываем в таблицу
// Собираем только стандартные типы и Enums
foreach (var item in list)
{
//При попытке экспорта пустого Json или Xml получаем ексепшн, поэтому игнорируем их
if (new[] { "Json", "Xml" }.Contains(item))
continue;
//project.SendInfoToLog(item);
// Получаем свойство по имени
System.Reflection.PropertyInfo prop = obj.GetType().GetProperty(item);
// Проверяем сколько надо параметров на вход, чтобы получить значение, делаем чтобы не ловить исключения
Object[] paramObj = { };
for (var i = 0; i < prop.GetGetMethod().GetParameters().Length; i++)
{
Array.Resize(ref paramObj, i + 1);
paramObj[i] = null;
}
Object val;
try
{
//Получаем значение
//project.SendInfoToLog(prop.PropertyType.ToString());
val = prop.GetValue(obj, paramObj);
}
catch (Exception e)
{
// Иногда выскакивает этот ексепшн, откуда х.з. но при обращении руками к такому методу тож его получаем, сделал такой костыль
//project.SendInfoToLog(e.GetType().Name);
if (e.GetType().Name != "TargetInvocationException")
{
//Если ексепшн другого типа, то вываливаемся по ошибке
throw;
}
continue;
}
table.Columns.Add(new System.Data.DataColumn(item, typeof(string)));
if (val != null)
valueRow[item] = val.ToString();
else
valueRow[item] = "NULL";
typeRow[item] = prop.PropertyType.ToString();
}
table.Rows.Add(valueRow);
table.Rows.Add(typeRow);
return table;
}
/// <summary>
/// Преобразуем словарь в DataTable, для дальнейшей записи в xml
/// </summary>
/// <param name="dictionary"></param>
/// <param name="tableName"></param>
/// <returns></returns>
private static DataTable DictionaryToDataTable(Dictionary<string, dynamic> dictionary, string tableName = "Dictionary")
{
DataTable table = new DataTable(tableName);
System.Data.DataRow valueRow = table.NewRow();
System.Data.DataRow typeRow = table.NewRow();
foreach (var keyValuePair in dictionary)
{
table.Columns.Add(new DataColumn(keyValuePair.Key, typeof(string)));
valueRow[keyValuePair.Key] = keyValuePair.Value.ToString();
typeRow[keyValuePair.Key] = keyValuePair.Value.GetType().ToString();
}
table.Rows.Add(valueRow);
table.Rows.Add(typeRow);
return table;
}
/// <summary>
/// Преобразуем локальные переменные в DataTable
/// </summary>
/// <param name="localVariables"></param>
/// <returns></returns>
private static DataTable LocalVariablesToDataTable(ILocalVariables localVariables)
{
Dictionary<string, dynamic> dictionary = localVariables.ToDictionary(a => a.Key, a => (dynamic)a.Value.Value);
return DictionaryToDataTable(dictionary, "LocalVariables");
}
/// <summary>
/// Собираем глобальные переменные и запихиваем их в DataTable
/// </summary>
/// <param name="globalVariables"></param>
/// <param name="separator">Разделитель между пространством имен и именем переменной, по умолчанию ","</param>
/// <returns></returns>
private static DataTable GlobalVariablesToDataTable(IGlobalVariables globalVariables, string separator = ",")
{
Dictionary<string, dynamic> dictionary = new Dictionary<string, dynamic>();
// Т.к. в зеннопостере у глобальных переменных нет возможности получить их список напрямую будем использовать рефлексию.
// Получаем список глобальных переменных, он хранится в свойстве AllTrackedVariables, напрямую недоступном
object list = globalVariables.GetType().GetProperty("AllTrackedVariables").GetValue(globalVariables, null);
// Т.к. мы не можем преобразовать этот список из object в List<IGlobalVariable>, пойдем другим путем
// Получаем количество записей в списке
int listCount = (int)list.GetType().GetProperty("Count").GetValue(list, null);
// Дергаем каждое значение в цикле, и принудительно ставим ему тип IGlobalVariable
for (int i = 0; i < listCount; i++)
{
// Получаем значение переменной с индексом i и приводим ее к типу IGlobalVariable
IGlobalVariable item = (IGlobalVariable)list.GetType().GetMethod("get_Item").Invoke(list, new object[] { i });
//Добавляем значения в словарь
dictionary.Add(item.Namespace + separator + item.Name, item.Value);
}
return DictionaryToDataTable(dictionary, "GlobalVariables");
}
/// <summary>
/// Преобразуем project.Context в DataTable, там где значения стандартных типов
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static DataTable ContextToDataTable(IContext context)
{
Dictionary<string, dynamic> dictionary = context.Keys.ToDictionary(a => a, a => context[a]);
return DictionaryToDataTable(dictionary, "Context");
}
/// <summary>
/// Собираем данные инстанса, необходимые для подключения к нему из Visual Studio и запихиваем их в DataTable
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
private static DataTable InstanceToDataTable(Instance instance)
{
Dictionary<string, dynamic> dictionary = new Dictionary<string, dynamic>();
dictionary.Add("Url", instance.Url);
dictionary.Add("Port", instance.Port);
dictionary.Add("Address", instance.Address);
return DictionaryToDataTable(dictionary, "Instance");
}
/// <summary>
/// Експортируем в файл профиль, локальные переменные и т.п.
/// </summary>
/// <param name="project"></param>
/// <param name="instance"></param>
/// <param name="path"></param>
public static void Run(IZennoPosterProjectModel project, Instance instance, string path = null)
{
try
{
//Путь куда сохраняем файл
path = path ?? System.IO.Path.GetTempPath() + "zpdata" + instance.Port + ".xml";
DataSet dataSet = new DataSet("Zenno");
dataSet.Tables.Add(ObjectPropertiesToDataTable(project, "Project")); // Проект
dataSet.Tables.Add(ObjectPropertiesToDataTable(project.Profile, "Profile")); // Профиль
dataSet.Tables.Add(LocalVariablesToDataTable(project.Variables)); // Локальные переменные
dataSet.Tables.Add(GlobalVariablesToDataTable(project.GlobalVariables)); // Глобальные переменные
dataSet.Tables.Add(ContextToDataTable(project.Context)); // Context
dataSet.Tables.Add(InstanceToDataTable(instance)); // Instance
dataSet.WriteXml(path);
project.SendInfoToLog("DEBUG: Сохранили слепок в файл: " + path);
}
catch (Exception e)
{
project.SendErrorToLog("Ошибка при експорте проекта:\n" + e.ToString());
throw;
}
}
}
}
Код:
ZennoDebug.Export.Run(project,instance);
Экспорт, сделали, дальше берем студию, и запускаем в ней отладочный проект (ZennoDebug), который содержит в себе метод импорта и нашу реализацию project.
Здесь у нас для разных версий ZP может возникнуть небольшая проблемка, т.к. IZennoPosterProjectModel от версии к версии немного меняется, и в одной версии он один в другой чуть другой, в приложеном проекте можно переключаться между ZP 5.9.9.1 и ZP 5.10.7.0
Для других версий внести правки, думаю у вас труда не составит, т.к. студия все что не так подсветит и при желании даже сама добавит.
В итоге в основной программе имеем такой код:
C#:
namespace ZennoDebug
{
class Program
{
static void Main(string[] args)
{
#if D5991
//Для ZP 5.9.9.1 и ниже
GlobalSettings.Init(@"C:\Program Files (x86)\ZennoLab\ZennoPoster Pro\Progs\Settings\globalsettings.settings");
Global.Variables.IsProjectMaker = true;
#endif
#if D51070
//Для ZP 5.10.2.0 и выше
GlobalSettings.Init(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),@"ZennoLab\ZennoPoster\5\Settings\globalsettings.settings"));
#endif
IZennoPosterProjectModel project = new ZennoPosterProject();
//Порт инстанса, у ProjectMaker он всегда 50606
int instancePort = 50606;
ZennoLab.CommandCenter.Instance instance = ZennoDebug.Import.Run(project, instancePort);
Console.WriteLine(TestSnippet(project, instance).ToString());
Console.ReadLine();
}
/// <summary>
/// Сюда пихаем код сниппета
/// </summary>
/// <param name="project"></param>
/// <param name="instance"></param>
/// <returns></returns>
public static dynamic TestSnippet(IZennoPosterProjectModel project, Instance instance)
{
Tab tab = instance.ActiveTab;
tab.Navigate("http://ya.ru");
return "OK";
}
/////////////////////////////////////////////////////////////////////////////
// для подключения из c# interactive
/*
using Global;
using ZennoLab.CommandCenter;
using ZennoLab.InterfacesLibrary.ProjectModel;
using ZennoLab.InterfacesLibrary.ProjectModel.Collections;
//GlobalSettings.Init(@"C:\Program Files (x86)\ZennoLab\ZennoPoster Pro\Progs\Settings\globalsettings.settings");
//Global.Variables.IsProjectMaker = true;
GlobalSettings.Init(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),@"ZennoLab\ZennoPoster\5\Settings\globalsettings.settings"));
IZennoPosterProjectModel project = new ZennoPosterProject();
ZennoLab.CommandCenter.Instance instance = ZennoDebug.Import.Run(project, 50606);
Tab tab = instance.ActiveTab;
*/
/////////////////////////////////////////////////////////////////////////////
}
}
А так как у нас есть подключение к инстансу, мы еще можем очень удобно работать в окне C# interactive, и тут-же видеть результат в браузере.
Я например 90% xPath запросов в нем и составляю, затем прогон кода через отладчик, и только потом вносим изменения в проект в ProjectMakere.
И на десерт, что делать если у нас плавающие проблемы, и отловить их в ProjectMaker, ну никак не получается.
Не проблема будем ловить их в ZennoPoster, и также подклюаться к нему как и к ProjectMaker.
Для этого в бэденд вставляем такой код:
C#:
/// Отобразить окно инстанса и ждать отладки пользователем, если включено в настройках
if (project.Variables["WaitForDebug"].Value == "True")
{
int waitTimeout = Convert.ToInt32(project.Variables["WaitForDebugTimeout"].Value);
ZennoDebug.Export.Run(project, instance);
project.SendInfoToLog("Ждем отладки " + project.Variables["WaitForDebugTimeout"].Value + " секунд, порт: "+instance.Port.ToString());
//Шлем себе алерт
//SendAlert(String.Format("{0} ({1}) Инстанс ожидает отладки, ActionId: {3}, Port: ",Environment.MachineName, Path.GetFileNameWithoutExtension(project.Name), +project.GetLastError().ActionId, instance.Port.ToString()));
instance.WaitForUserAction(waitTimeout);
// Если в заголовок добавился DEBUG, мы начали отладку
if (instance.FormTitle.Contains("DEBUG"))
{
project.SendInfoToLog("Инстанс в режиме отладки",true);
instance.WaitForUserAction(500000);
project.SendInfoToLog("Завершили режим отладки",true);
}
}
Надо это для того, чтобы в многопотоке не получить кучу вечно ожидающих отладки инстансов.
При подключении к инстансу из студии нам достаточно добавить в заголовок инстанса слово "DEBUG", чтобы проект знал что им занимаются и закрываться не надо.
C#:
instance.AddToTitle("DEBUG");
В проекте студии меняем порт инстанса на нужный нам, и спокойно подключаемся для разбора проблемы.
На этом все.
P.S. Если надо использовать ZennoPoster класс, например распознавание капчи, запускайте студию под администратором, иначе будете ломать голову почему не работает.
P.P.S. Я не ошибся разделом, смысла кидать эту статью на конкурс я не вижу, да и все красиво расписывать откровенно лень, но т.к. ребята давно просили выложить, нашел в себе силы и немного времени расписать )
- Категория
- Полезно
Вложения
-
131,1 КБ Просмотры: 542
-
29,4 КБ Просмотры: 464
Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.