"""Responds to flow generation HTTP requests.""" import bottle from aist_common.log import get_logger from bottle import request from services.test_generator_service import TestGeneratorService LOGGER = get_logger('test_generator_controller') class TestGeneratorController: """Responds to flow generation HTTP requests.""" def __init__(self, app): """ Initializes the TestGeneratorController class. :param app: The Bottle app. """ self._app = app self._service = TestGeneratorService() def add_routes(self): """ Add request routes to the Bottle app. """ self._app.route('/v1/status', method="GET", callback=self.get_status) self._app.route('/v1/predict', method="POST", callback=self.predict) @staticmethod def get_status(): """ Get service status.
""" Combines information from page classifiers along with an abstract test flow to generate possible concrete test flows for the SUT.""" import itertools from flow_execution.concrete_test_flow import ConcreteTestFlow from aist_common.log import get_logger LOGGER = get_logger('flow-planner') class FlowPlanner: """ Combines information from page classifiers along with an abstract test flow to generate possible concrete test flows for the SUT.""" def __init__(self): """ Initializes the FlowPlanner class. """ self._klass = __class__.__name__ @staticmethod def plan(abstract_state, page_analysis, abstract_flow): """ Given an abstract test flow and widget classifications, produces a list of possibly executable concrete test flows. :param abstract_state: The current abstract state (where a concrete test flow execution would begin from). :param page_analysis: The page analysis output for the current abstract state (element classifications). :param abstract_flow: The abstract test flow to process. :return: A list of concrete test flows.
"""The ConcreteStateFeaturizer class (responsible for extracting features from a concrete state) and accompanying helper functions.""" import math import re import numpy as np import pandas as pd from colormath.color_conversions import convert_color from colormath.color_diff import delta_e_cie2000 from colormath.color_objects import sRGBColor, LabColor from aist_common.log import get_logger LOGGER = get_logger('concrete_state_featurizer') color_re = re.compile(r'(rgb|rgba)\(([0-9]+?), ([0-9]+?), ([0-9]+?)([,)])') base_colors = [ ('black', np.array([0, 0, 0])), ('white', np.array([255, 255, 255])), ('red', np.array([255, 0, 0])), ('blue', np.array([0, 0, 255])), ('grey', np.array([128, 128, 128])), ('yellow', np.array([255, 255, 0])), ('orange', np.array([255, 165, 0])), ('green', np.array([0, 255, 0])), ('purple', np.array([128, 0, 128])), ] basic_html_tags = [
"""A client that communicates with and provides the capabilities of the flow generation service.""" import os import requests import urllib3 from aist_common.log import get_logger urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) LOGGER = get_logger('flow-generator-client') class FlowGeneratorClient: """A client that communicates with and provides the capabilities of the flow generation service.""" FLOW_GEN_URL = "{}/v1/predict" def __init__(self): """ Initializes the FlowGeneratorClient class. """ self._klass = "FlowGeneratorClient" self._set_envs() def _set_envs(self): """ Loads environment variables. """ self.SERVICE_URL = 'http://flow-generator' if 'FLOW_GENERATION_URL' in os.environ:
"""A client that communicates with and provides the capabilities of the runner service.""" import json import os import time from aeoncloud import get_session_factory from aeoncloud.exceptions.aeon_session_error import AeonSessionError from aist_common.log import get_logger LOGGER = get_logger('runner-client') class RunnerClient: """A client that communicates with and provides the capabilities of the runner service.""" GET_DOCUMENT_LOC = "return document.location.href;" HAS_JQUERY_SCRIPT = "if(window.jQuery) return true; return false;" FIX_JQUERY_SCRIPT = "if(window.jQuery && !window.jquery) window.jquery = window.jQuery;" def __init__(self, runner_url): """ Initializes the RunnerClient class. :param runner_url: The URL to the Aeon runner to be used. """ self._klass = "RunnerClient" self.RUNNER_URL = runner_url self.session = None self.aeon = get_session_factory()
"""Responds to gateway HTTP requests.""" import bottle from aist_common.log import get_logger from bottle import request from services.gateway_service import GatewayService LOGGER = get_logger('gateway-controller') class GatewayController: """Responds to gateway HTTP requests.""" def __init__(self, app): """ Initializes the GatewayController class. :param app: The Bottle app. """ self._app = app self._service = GatewayService() def add_routes(self): """ Add request routes to the Bottle app. """ self._app.route('/v1/status', method="GET", callback=self.get_status) self._app.route('/v1/start', method="POST", callback=self.start_session) self._app.route('/v1/stop', method="POST", callback=self.stop_session) @staticmethod
"""A client that communicates with and provides the capabilities of the page analysis service.""" import os import requests import urllib3 from aist_common.log import get_logger urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) LOGGER = get_logger('page-analysis-client') class PageAnalysisClient: """A client that communicates with and provides the capabilities of the page analysis service.""" ANALYSIS_RUN_URL = "{}/v1/pageAnalysis/state/concrete" def __init__(self): """ Initializes the PageAnalysisClient class. """ self._klass = "PageAnalysisClient" self._set_envs() def _set_envs(self): """ Loads environment variables. """ self.SERVICE_URL = 'http://page-analyzer' if 'PAGE_ANALYSIS_URL' in os.environ:
from clients.flow_generation_client import FlowGeneratorClient from clients.form_expert_client import FormExpertClient from clients.page_analysis_client import PageAnalysisClient from clients.runner_client import RunnerClient from defects.defect_reporter import DefectReporter from flow_execution.flow_executor import FlowExecutor from flow_execution.flow_planner import FlowPlanner from memory.priority_memory import PriorityMemory from outbound_tasks import PlannedFlowPublisher from perceive.label_extraction import LabelExtraction from perceive.state_observer import StateObserver from aist_common.log import get_logger from memory.agent_memory import * LOGGER = get_logger('agent-loop') class AgentLoop: """ Responsible for implementing the core control flow of the agent. Observes the SUT environment, plans, and acts upon the environment.""" NUM_ITERATIONS = 300 def __init__(self, sut_url, runner_url, form_expert_client=None, runner_client=None, page_analysis_client=None, flow_generator_client=None,
"""Handles abstract test flow generation requests.""" import os import random import json import numpy as np import tensorflow as tf from aist_common.log import get_logger from keras.models import load_model LOGGER = get_logger('test-generator-service') BASE_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'data')) class TestGeneratorService: """Handles abstract test flow generation requests.""" def __init__(self): """ Initializes the TestGeneratorService class. """ with open("{}/embedding.json".format(BASE_PATH)) as f: data = json.load(f) self.char_indices = data['char_indices'] self.indices_char = data['indices_char'] self.maxlen = 11 self.chars = self.char_indices.values() self.model = load_model("{}/lstm.h5".format(BASE_PATH))
"""Responds to page analysis HTTP requests.""" import json import bottle from aist_common.log import get_logger from bottle import request from services.page_analysis_service import PageAnalysisService LOGGER = get_logger('page_analysis_controller') class PageAnalysisController: """Responds to page analysis HTTP requests.""" def __init__(self, app, service=None): """ Initializes the PageAnalysisController class. :param app: The Bottle app. :param service: An instance of the PageAnalysisService class. """ self._app = app if service is not None: self._service = service else: self._service = PageAnalysisService() def add_routes(self): """ Add request routes to the Bottle app. """
"""Executes a concrete test flow.""" from form_strategies.fill_entire_form import FillEntireForm from aist_common.log import get_logger LOGGER = get_logger('flow-executor') class FlowExecutor: """Executes a concrete test flow.""" def __init__(self, form_expert, page_analyzer, state_abstracter, label_extracter, observer, defect_rep): """ Initializes the FlowExecutor class. :param form_expert: An instance of the form expert client. :param page_analyzer: An instance of the page analyzer client. :param state_abstracter: An instance of the StateAbstracter class. :param label_extracter: An instance of the LabelExtraction class. :param observer: An instance of the StateObserver class. :param defect_rep: An instance of the DefectReporter class. """ self.form_expert = form_expert self.page_analyzer = page_analyzer self.state_abstracter = state_abstracter self.ext_labels = label_extracter self.observer = observer self.defect_rep = defect_rep self.form_fill_strategy = FillEntireForm(form_expert) self._klass = __class__.__name__
"""A form filling strategy that attempts to fill out all settable form fields with valid values.""" from aist_common.log import get_logger LOGGER = get_logger('fill-entire-form-strategy') class FillEntireForm: """A form filling strategy that attempts to fill out all settable form fields with valid values.""" def __init__(self, form_expert): """ Initializes the FillEntireForm class. :param form_expert: An instance of the form expert client. """ self.form_expert = form_expert self._klass = __class__.__name__ def execute(self, runner, abstract_state): """ Executes the form filling strategy against a given abstract state. :param runner: An instance of the runner client (that holds an active runner session). :param abstract_state: The current abstract state for the page that needs to be filled out. :return: True if all settable form fields were successfully filled. """ actionable_widgets = [w for w in abstract_state.widgets if 'set' in w['actions']] actionable_widgets = self.form_expert.get_concrete_values(actionable_widgets) for actionable_widget in actionable_widgets:
"""Acts a single entry-point to all other services, handling requests and routing accordingly to other services.""" import uuid from aist_common.CeleryConfig.celery_app import create_app from aist_common.log import get_logger LOGGER = get_logger('gateway-service') class GatewayService: """Acts a single entry-point to all other services, handling requests and routing accordingly to other services.""" @staticmethod def start_session(request_payload): """ Handles a request to start an exploration/testing session. :param request_payload: A payload which contains the information necessary to start a session. :return: The session ID for the newly started session. """ app = create_app([]) @app.task(name='test_agent.start_session', queue="agent_broadcast_tasks") def start_agent_session(_): pass session_id = uuid.uuid4().hex request = {'SUT_URL': request_payload['SUT_URL']}
"""The main Agent entry-point. Contains all necessary Celery plumbing code.""" import threading import uuid import celery.worker from aist_common.CeleryConfig.celery_app import create_app from aist_common.log import get_logger LOGGER = get_logger('agent-explore-and-test') def main(): LOGGER.info("Starting agent...") app = create_app(['inbound_tasks', 'outbound_tasks']) worker = celery.worker.WorkController( app=app, hostname="test-agent-" + uuid.uuid4().hex, pool_cls='solo', queues=['test_agent_queue', 'agent_broadcast_tasks']) threading.Thread(target=worker.start).start() LOGGER.info("Celery started.") LOGGER.info("Agent started.") if __name__ == '__main__': main()
"""Contains all inbound Celery tasks.""" import os import jsonpickle from aist_common.CeleryConfig.celery_app import create_app from aist_common.log import get_logger from memory.agent_memory import * from loop.agent_loop import AgentLoop LOGGER = get_logger('inbound-tasks') app = create_app([]) # INBOUND TASKS. @app.task(name='test_agent.start_session', queue="agent_broadcast_tasks") def start_session(session_start_data): """ A Celery task that starts an agent session. :param session_start_data: The request payload. :return: False if environment variable RUNNER_URL not setup properly; otherwise, True. """ LOGGER.info("Starting session.") sut_url = session_start_data['SUT_URL'] if 'RUNNER_URL' not in os.environ:
import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, classification_report, confusion_matrix from services.concrete_state_featurizer import ConcreteStateFeaturize from services.confusion_matrix import print_cm from services.frame_mapper import FrameMapper from aist_common.log import get_logger from aist_common.pickler import ReadWritePickles RUN_LIVE = True BASE_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'data')) LOGGER = get_logger('page_analysis_service') PICKLER = ReadWritePickles() class PageAnalysisService: """Handles web-page element classification and training requests.""" def __init__(self, base_path=None): """ Initializes the PageAnalysisService class. :param base_path: An optionally provided base path from which to load pickled classifiers from. """ self.__featurize = ConcreteStateFeaturize() self.__frame_mapper = FrameMapper() self.feature_mapping = {
"""Provides methods for retrieving best matches for form fields""" import random import sys from Levenshtein import seqratio from aist_common.log import get_logger LOGGER = get_logger('classifier') def levenshtein_distance(a, b): """ calculates the Levenshtein distance of two instances :param a: The first instance to compare. :param b: The second instance to compare. :return: The Levenshtein distance of the two instances. """ ratio = seqratio(a, b['features']) return 1 - ratio def get_neighbor(training_set, test_instance): """ Returns a nearest neighbor out of a given set. If there are multiple neighbors with the same smallest distance, one is chosen randomly. :param training_set: The set of instances to choose from. :param test_instance: The instance to which a nearest neighbor should be found.
"""The main Agent entry-point. Contains all necessary Celery plumbing code.""" import threading import celery.worker from aist_common.CeleryConfig.celery_app import create_app from aist_common.log import get_logger LOGGER = get_logger('agent-coordinator') def main(): LOGGER.info("Starting agent...") app = create_app(['inbound_tasks', 'outbound_tasks']) worker = celery.worker.WorkController(app=app, hostname="test-coordinator", pool_cls='solo', queues=['test_coordinator_queue']) threading.Thread(target=worker.start).start() LOGGER.info("Celery started.") LOGGER.info("Agent started.") if __name__ == '__main__': main()
import json import random import requests import os from aist_common.log import get_logger LOGGER = get_logger('form-expert-client') class FormExpertClient: def __init__(self): self.FORM_EXPERT_URL = 'http://form-expert' if 'FORM_EXPERT_URL' in os.environ: self.FORM_EXPERT_URL = os.environ['FORM_EXPERT_URL'] self.FILL_FORM_URL = '{}/api/v1/fill_form'.format(self.FORM_EXPERT_URL) @staticmethod def get_input_types(): return [ 'VALID', 'BLANK', 'WHITESPACE', 'INVALID_LONG', 'INVALID_SPECIAL_CHARACTERS', 'INVALID_XSR' ] def get_concrete_inputs(self, label, input_class): if input_class == 'VALID': return self.get_concrete_value(label) elif input_class == 'BLANK': values = [''] return random.choice(values)