Отладка проектов в VisualStudio, упрощаем себе жизнь.

Adigen

Client
Регистрация
28.07.2014
Сообщения
825
Реакции
654
Баллы
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. В проекте в любом нужном нам месте делаем экспорт, в студии делаем импорт, и спокойно работаем с кодом.

Скрипт экспорта, добавляем в общий код:
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;
        */
        /////////////////////////////////////////////////////////////////////////////
    }
}

В метод TestSnippet, вставляем код нужного нам для отладки кубика, и спокойно дебажим.

А так как у нас есть подключение к инстансу, мы еще можем очень удобно работать в окне 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);
    }
}

Код проверяет, включен ли режим отладки для данного проекта (WaitForDebug), и если включен отображает нам браузер, шлет алерт куда нам надо (это уже сами доделаете) и дает нам определенное время подключиться к нему из студии (WaitForDebugTimeout).

Надо это для того, чтобы в многопотоке не получить кучу вечно ожидающих отладки инстансов.
При подключении к инстансу из студии нам достаточно добавить в заголовок инстанса слово "DEBUG", чтобы проект знал что им занимаются и закрываться не надо.
C#:
Развернуть Свернуть Копировать
instance.AddToTitle("DEBUG");

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

На этом все.

P.S. Если надо использовать ZennoPoster класс, например распознавание капчи, запускайте студию под администратором, иначе будете ломать голову почему не работает.

P.P.S. Я не ошибся разделом, смысла кидать эту статью на конкурс я не вижу, да и все красиво расписывать откровенно лень, но т.к. ребята давно просили выложить, нашел в себе силы и немного времени расписать )
 
Категория
  1. Полезно

Вложения

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

А если баги еще и плавающие, да и не обязательно наши, например подглючивает сайт и выдает не то что должен, да еще и поймать этот момент в 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;
        */
        /////////////////////////////////////////////////////////////////////////////
    }
}

В метод TestSnippet, вставляем код нужного нам для отладки кубика, и спокойно дебажим.

А так как у нас есть подключение к инстансу, мы еще можем очень удобно работать в окне 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);
    }
}

Код проверяет, включен ли режим отладки для данного проекта (WaitForDebug), и если включен отображает нам браузер, шлет алерт куда нам надо (это уже сами доделаете) и дает нам определенное время подключиться к нему из студии (WaitForDebugTimeout).

Надо это для того, чтобы в многопотоке не получить кучу вечно ожидающих отладки инстансов.
При подключении к инстансу из студии нам достаточно добавить в заголовок инстанса слово "DEBUG", чтобы проект знал что им занимаются и закрываться не надо.
C#:
Развернуть Свернуть Копировать
instance.AddToTitle("DEBUG");

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

На этом все.

P.S. Если надо использовать ZennoPoster класс, например распознавание капчи, запускайте студию под администратором, иначе будете ломать голову почему не работает.

P.P.S. Я не ошибся разделом, смысла кидать эту статью на конкурс я не вижу, да и все красиво расписывать откровенно лень, но т.к. ребята давно просили выложить, нашел в себе силы и немного времени расписать )
Зачетная статья, чего еще можно ожидать от зубатого котЭ! Не то что как я заработал на квартиру! В общем +
 
  • Спасибо
Реакции: Adigen
Adigen а что в конкурс статей слабо было такую полезную статью оформить? Или типа конкурс не для тебя?
 
Adigen а что в конкурс статей слабо было такую полезную статью оформить? Или типа конкурс не для тебя?
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
 
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

За статью - преогромное спасибо! Наконец-то можно будет поотловить некоторые баги и понять почему валятся такие штуки в откровенно неожиданных местах:
Код:
Развернуть Свернуть Копировать
Ошибка обращения к Instance.ActiveTab


И ещё раз повторю и за других: эту бы статью, да на конкурс! Крайне полезная информация для тех, кто знает как её использовать :ay:
 
  • Спасибо
Реакции: zennoX
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
разочаровался, не разочаровался, а хорошая статья должна быть в конкурсе, чтобы сразу было видно различие между шлаком и хорошим материалом ))) Приз дело третье )))
 
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

За статью - преогромное спасибо! Наконец-то можно будет поотловить некоторые баги и понять почему валятся такие штуки в откровенно неожиданных местах:
Код:
Развернуть Свернуть Копировать
Ошибка обращения к Instance.ActiveTab


И ещё раз повторю и за других: эту бы статью, да на конкурс! Крайне полезная информация для тех, кто знает как её использовать :ay:
instance с маленькой буквы.
 
В общем статья не для всех ))) и поэтому я закинул ее в архив, а то мало ли что!
Кому нужно пишите - найдется все но за бабки )))
 
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

За статью - преогромное спасибо! Наконец-то можно будет поотловить некоторые баги и понять почему валятся такие штуки в откровенно неожиданных местах:
Код:
Развернуть Свернуть Копировать
Ошибка обращения к Instance.ActiveTab


И ещё раз повторю и за других: эту бы статью, да на конкурс! Крайне полезная информация для тех, кто знает как её использовать :ay:
Это необрабатываемая ошибка. Она не фиксируется даже в bad end - следовательно это ошибка внутренняя
 
instance с маленькой буквы.
Это копипаста из лога ZP)

Это необрабатываемая ошибка. Она не фиксируется даже в bad end - следовательно это ошибка внутренняя
Вот сейчас ты меня таким сообщением немного ставишь в тупик... Я не проверял наверняка, но по-моему, внутренние ошибки должны валиться в bad end, хотя может ты и прав - нужно посмотреть.
 
Вот сейчас ты меня таким сообщением немного ставишь в тупик... Я не проверял наверняка, но по-моему, внутренние ошибки должны валиться в bad end, хотя может ты и прав - нужно посмотреть.
Ну я имею ввиду, что эта ошибка идет со стороны браузера или зеннопостера, а не со стороны управляющего им кода, который мы пишем.
 
Ну я имею ввиду, что эта ошибка идет со стороны браузера или зеннопостера, а не со стороны управляющего им кода, который мы пишем.
Всё верно, но я хочу поймать момент этой ошибки, т.к. часто они возникают в раедомных местах и такое чувство, что предыдущий поток - убивает процесс, а текущий поток в это время инициализируется и просто еще не запустил браузер или не работает с браузером N-секунд (но далее будет)
 
Всё верно, но я хочу поймать момент этой ошибки, т.к. часто они возникают в раедомных местах и такое чувство, что предыдущий поток - убивает процесс, а текущий поток в это время инициализируется и просто еще не запустил браузер
Эта ошибка закрытия табов. У меня она практически исчезла тогда, когда я поставил задержку между закрытиями табов в 1 секунду.
 
Эта ошибка закрытия табов. У меня она практически исчезла тогда, когда я поставил задержку между закрытиями табов в 1 секунду.
я их не закрываю и не юзаю табы)) в том и дело)
 
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
Только хотел написать: "Голос, ваш", как оказалось что статья вне конкурса. Печаль.:(
Определённо, статью на конкурс!!!

P.S. На Win10 в PM 5.10.2.0-5.10.4.0 отладка в VS не работает, из-за бага в zp.
 
Последнее редактирование:
Доброго времени суток!
Вопрос: можно как нибудь реализовать метод SendInfoToLog, что бы он работал так же как зеновский, выводил текст в лог?
И еще, ZennoPoster.HttpGet, он не отрабатывает, результат пустой)
Запускал с правами администратора(Win 10, VS 2017)
 
Последнее редактирование:
Доброго времени суток!
Вопрос: можно как нибудь реализовать метод SendInfoToLog, что бы он работал так же как зеновский, выводил текст в лог?
И еще, ZennoPoster.HttpGet, он не отрабатывает, результат пустой)
Запускал с правами администратора(Win 10, VS 2017)
Нет, т.к. у нас локальная копия объекта, project.
И если честно не пойму зачем это надо ? все логи выводятся в консоль, достаточно просто включить ее в студии.
 
Нет, т.к. у нас локальная копия объекта, project.
И если честно не пойму зачем это надо ? все логи выводятся в консоль, достаточно просто включить ее в студии.
А по поводу ZennoPoster.HttpGet что можете сказать?
 
А по поводу ZennoPoster.HttpGet что можете сказать?
Он работает, просто надо чтобы был сам зеннопостер запущен еще, именно ZennoPoster.
Сейчас в топике поправлю, забыл об этом ньюансе.

UPD. Топик править уже не дает (
Так-что, знать будут только самые читающие ))
 
Снимаю шляпу! Авторам программы - две просьбы: выдать автору статьи спец. приз и интегрировать эту фичу в стандартный функционал. Благо, всё уже реализовано)
 
Снимаю шляпу! Авторам программы - две просьбы: выдать автору статьи спец. приз и интегрировать эту фичу в стандартный функционал. Благо, всё уже реализовано)
В стандартном функционале идельно было-бы дать возможность подключения к project, так-же как и к инстансу, тогда в студии для отладки надо будет всего 2 строчки кода для подключения к объектам.

Буду посовободнее, может запилю прокси объект для прямого подключения, но это явно не в ближайшее время.
 
  • Спасибо
Реакции: Yuriy Zymlex и Lord_Alfred
Я вот открыл solution, у меня не подтянулись references. Будет полезно добавить пару слов, где находятся файлы для Global, ZennoLab.CommandCenter, ZennoLab.InterfacesLibrary (в папке Zenno Poster \ Progs)
У меня в файле ZennoPosterProjectModel.cs пара пропертей не загрузилась, потому что зенка старая, в таком случае можно соответствующие строчки просто удалить.
И ещё, где я не нашёл чего-то типа #define D5991, это где объявляется?
 
Я вот открыл solution, у меня не подтянулись references. Будет полезно добавить пару слов, где находятся файлы для Global, ZennoLab.CommandCenter, ZennoLab.InterfacesLibrary (в папке Zenno Poster \ Progs)
У меня в файле ZennoPosterProjectModel.cs пара пропертей не загрузилась, потому что зенка старая, в таком случае можно соответствующие строчки просто удалить.
И ещё, где я не нашёл чего-то типа #define D5991, это где объявляется?

Файлы находятся в папке с ЗенноПостером, а где зеннопостер это уже зависит от версии и куда ставили )
D5991 объявляется в свойствах проекта -> сборка - > Символы условной компиляции.
В данном случае они помогают переключаться между 2 мя версиями зенки (5.9.9.1 и 5.10.7.0) без дополнительных телодвижений.
Если надо добавить новую версию, просто добавляем еще одну конфигурацию, прописываем там подобную переменную и включаем или выключаем ей определенные куски кода.
Затем в файле ZennoDebug.csproj ручками добавляем еще один путь к дллкам для новой конфигурации:
Код:
Развернуть Свернуть Копировать
<Reference Include="ZennoLab.CommandCenter">
      <HintPath Condition="'$(Configuration)'=='Debug 5.9.9.1'">C:\Program Files (x86)\ZennoLab\ZennoPoster Pro\Progs\ZennoLab.CommandCenter.dll</HintPath>
      <HintPath Condition="'$(Configuration)'=='Debug 5.10.7.0'">C:\Program Files (x86)\ZennoLab\RU\ZennoPoster Pro\5.10.7.0\Progs\ZennoLab.CommandCenter.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="ZennoLab.InterfacesLibrary">
      <HintPath Condition="'$(Configuration)'=='Debug 5.9.9.1'">C:\Program Files (x86)\ZennoLab\ZennoPoster Pro\Progs\ZennoLab.InterfacesLibrary.dll</HintPath>
      <HintPath Condition="'$(Configuration)'=='Debug 5.10.7.0'">C:\Program Files (x86)\ZennoLab\RU\ZennoPoster Pro\5.10.7.0\Progs\ZennoLab.InterfacesLibrary.dll</HintPath>
      <Private>True</Private>
    </Reference>
 
KvwU6i.png

вот такая ошибка когда пробую отдебажить через прикрепленный шаблон. Снипет свой вставил в код. В чем дело, как лечить?
Сначала открыл нужную страницу в ПМ, потом запустил снипет из шаблона, он выполнился без ошибок, затем зашел в визуал студию и нажал Пуск
Версия 5.10.6.0
 
Последнее редактирование:

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)