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

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
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. Я не ошибся разделом, смысла кидать эту статью на конкурс я не вижу, да и все красиво расписывать откровенно лень, но т.к. ребята давно просили выложить, нашел в себе силы и немного времени расписать )
 
Категория
Полезно

Вложения

Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

Борат Сагдиев

Пользователь
Регистрация
09.05.2017
Сообщения
61
Благодарностей
36
Баллы
8

Обращаем Ваше внимание на то, что данный пользователь заблокирован.
Не рекомендуем проводить с Борат Сагдиев какие-либо сделки.

тысячу плюсов за статью.
 
Регистрация
08.07.2015
Сообщения
2 851
Благодарностей
712
Баллы
113
Все мы сталкиваемся с багами, они были, есть и будут есть нам мозг, особенно если мы взаимодействуем со сторонними ресурсами, например сайтами.

А если баги еще и плавающие, да и не обязательно наши, например подглючивает сайт и выдает не то что должен, да еще и поймать этот момент в 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
Регистрация
08.07.2015
Сообщения
2 851
Благодарностей
712
Баллы
113
Adigen а что в конкурс статей слабо было такую полезную статью оформить? Или типа конкурс не для тебя?
 

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
93
Adigen а что в конкурс статей слабо было такую полезную статью оформить? Или типа конкурс не для тебя?
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
 
  • Спасибо
Реакции: samsonnn, deopl и Dimionix

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 919
Благодарностей
3 809
Баллы
113
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

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

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

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
93
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

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

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

amyboose

Client
Регистрация
21.04.2016
Сообщения
2 311
Благодарностей
1 176
Баллы
113
Времена меняются, сейчас была премодерация статей, откровенное "фуфу" вроде бы не пустили)

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

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

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 919
Благодарностей
3 809
Баллы
113
instance с маленькой буквы.
Это копипаста из лога ZP)

Это необрабатываемая ошибка. Она не фиксируется даже в bad end - следовательно это ошибка внутренняя
Вот сейчас ты меня таким сообщением немного ставишь в тупик... Я не проверял наверняка, но по-моему, внутренние ошибки должны валиться в bad end, хотя может ты и прав - нужно посмотреть.
 

amyboose

Client
Регистрация
21.04.2016
Сообщения
2 311
Благодарностей
1 176
Баллы
113
Вот сейчас ты меня таким сообщением немного ставишь в тупик... Я не проверял наверняка, но по-моему, внутренние ошибки должны валиться в bad end, хотя может ты и прав - нужно посмотреть.
Ну я имею ввиду, что эта ошибка идет со стороны браузера или зеннопостера, а не со стороны управляющего им кода, который мы пишем.
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 919
Благодарностей
3 809
Баллы
113
Ну я имею ввиду, что эта ошибка идет со стороны браузера или зеннопостера, а не со стороны управляющего им кода, который мы пишем.
Всё верно, но я хочу поймать момент этой ошибки, т.к. часто они возникают в раедомных местах и такое чувство, что предыдущий поток - убивает процесс, а текущий поток в это время инициализируется и просто еще не запустил браузер или не работает с браузером N-секунд (но далее будет)
 

amyboose

Client
Регистрация
21.04.2016
Сообщения
2 311
Благодарностей
1 176
Баллы
113
Всё верно, но я хочу поймать момент этой ошибки, т.к. часто они возникают в раедомных местах и такое чувство, что предыдущий поток - убивает процесс, а текущий поток в это время инициализируется и просто еще не запустил браузер
Эта ошибка закрытия табов. У меня она практически исчезла тогда, когда я поставил задержку между закрытиями табов в 1 секунду.
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 919
Благодарностей
3 809
Баллы
113
Эта ошибка закрытия табов. У меня она практически исчезла тогда, когда я поставил задержку между закрытиями табов в 1 секунду.
я их не закрываю и не юзаю табы)) в том и дело)
 

sergodjan66

Administrator
Команда форума
Регистрация
05.09.2012
Сообщения
18 172
Благодарностей
8 036
Баллы
113

Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
5 883
Благодарностей
3 041
Баллы
113
Я после прошлого конкурса в нем сильно разочаровался, хотел еще тогда для него выложить, но если первые конкурсы были нормальными, то потом стали откровенный шлак пропускать, так что я решил туда принципиально ничего не писать.
Только хотел написать: "Голос, ваш", как оказалось что статья вне конкурса. Печаль.:(
Определённо, статью на конкурс!!!

P.S. На Win10 в PM 5.10.2.0-5.10.4.0 отладка в VS не работает, из-за бага в zp.
 
Последнее редактирование:

kirillpower

Client
Регистрация
07.03.2016
Сообщения
22
Благодарностей
7
Баллы
3
Доброго времени суток!
Вопрос: можно как нибудь реализовать метод SendInfoToLog, что бы он работал так же как зеновский, выводил текст в лог?
И еще, ZennoPoster.HttpGet, он не отрабатывает, результат пустой)
Запускал с правами администратора(Win 10, VS 2017)
 
Последнее редактирование:

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
93
Доброго времени суток!
Вопрос: можно как нибудь реализовать метод SendInfoToLog, что бы он работал так же как зеновский, выводил текст в лог?
И еще, ZennoPoster.HttpGet, он не отрабатывает, результат пустой)
Запускал с правами администратора(Win 10, VS 2017)
Нет, т.к. у нас локальная копия объекта, project.
И если честно не пойму зачем это надо ? все логи выводятся в консоль, достаточно просто включить ее в студии.
 

kirillpower

Client
Регистрация
07.03.2016
Сообщения
22
Благодарностей
7
Баллы
3
Нет, т.к. у нас локальная копия объекта, project.
И если честно не пойму зачем это надо ? все логи выводятся в консоль, достаточно просто включить ее в студии.
А по поводу ZennoPoster.HttpGet что можете сказать?
 

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
93
А по поводу ZennoPoster.HttpGet что можете сказать?
Он работает, просто надо чтобы был сам зеннопостер запущен еще, именно ZennoPoster.
Сейчас в топике поправлю, забыл об этом ньюансе.

UPD. Топик править уже не дает (
Так-что, знать будут только самые читающие ))
 

samsonnn

Client
Регистрация
02.06.2015
Сообщения
1 383
Благодарностей
1 077
Баллы
113
А почему не в конкурсных статьях? я голосую за ++++ молодец!
 

Nick

Client
Регистрация
22.07.2014
Сообщения
1 943
Благодарностей
772
Баллы
113
Снимаю шляпу! Авторам программы - две просьбы: выдать автору статьи спец. приз и интегрировать эту фичу в стандартный функционал. Благо, всё уже реализовано)
 

Adigen

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

Буду посовободнее, может запилю прокси объект для прямого подключения, но это явно не в ближайшее время.
 
  • Спасибо
Реакции: Zymlex и Lord_Alfred

Nick

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

Adigen

Client
Регистрация
28.07.2014
Сообщения
828
Благодарностей
648
Баллы
93
Я вот открыл 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>
 

Porosenok

Client
Регистрация
26.09.2010
Сообщения
1 208
Благодарностей
77
Баллы
48

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

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